前端 Serverless 架构的实践复盘:Cloudflare Workers 与 Vercel Edge 对比
一、前端切入 Serverless 的三个典型场景
前端团队探索 Serverless 架构的动机通常来自三类场景:API BFF 层(Backend For Frontend,为前端定制的聚合接口)、边缘渲染与个性化(基于用户地理/设备特征实时调整页面内容)、轻量级后端服务(表单提交、Webhook 处理、短链跳转等)。
这三类场景的共同特征是:不需要传统后端的长连接或事务性数据库,追求低延迟与就近部署,团队希望减少基础设施运维投入。过去两年,围绕 Edge Computing(边缘计算)概念形成了两条主流技术路线:以 Cloudflare Workers 为代表的 V8 Isolate 模型,以及以 Vercel Edge Functions 为代表的 Node.js 兼容层方案。
二、运行时差异:V8 Isolate vs Node.js 子集
2.1 Cloudflare Workers 运行时
Workers 运行在 V8 Isolate 之上,不包含 Node.js 运行时。这意味着没有 Buffer、process、fs 等 Node.js 核心模块。好处是冷启动极快(通常 < 5ms),内存隔离彻底;代价是大量 npm 包无法直接使用——任何依赖 Node.js 核心 API 的库都会在运行时抛出异常。
// Cloudflare Workers 中常见的不可用 API
// ❌ 以下在 Workers 中均不可用
// require('fs') — 文件系统
// require('net') — TCP 套接字
// require('child_process') — 子进程
// process.env — 使用 env 绑定替代
// Buffer — 使用 ArrayBuffer/TypedArray
2.2 Vercel Edge Functions 运行时
Vercel Edge Runtime 是基于 Web API 标准的 Node.js 子集,保留了部分 npm 包的兼容性。其核心约束为:不支持文件系统与原生模块;动态 require 受限;单个函数执行时间上限为 30 秒(Pro 计划)。相对 Workers,生态迁移成本更低——Next.js 项目的 API Routes 可以无缝切换。
2.3 兼容性适配层设计
在需要同时部署到两个平台的场景中,封装一个兼容性适配层是必要的:
// runtime-adapter.ts — Cloudflare Workers 与 Vercel Edge 的运行时适配层
/** 运行时环境类型 */
type RuntimeEnv = 'cloudflare-workers' | 'vercel-edge' | 'node' | 'unknown';
/** 检测当前运行时环境 */
export function detectRuntime(): RuntimeEnv {
// Cloudflare Workers 特征:全局 caches API、Request/Response 原生存在且无 process
if (
typeof caches !== 'undefined' &&
typeof caches.default !== 'undefined' &&
typeof process === 'undefined'
) {
return 'cloudflare-workers';
}
// Vercel Edge Runtime 特征:EdgeRuntime 全局标识
if (typeof EdgeRuntime === 'string') {
return 'vercel-edge';
}
// Node.js 环境
if (typeof process !== 'undefined' && process.versions?.node) {
return 'node';
}
return 'unknown';
}
/** 跨平台兼容的 fetch 增强器 — 自动处理超时与重试 */
export interface FetchWithRetryOptions {
/** 重试次数(默认 2 次) */
retries?: number;
/** 单次请求超时时间(毫秒,默认 8000ms) */
timeout?: number;
/** 可重试的 HTTP 状态码列表 */
retryStatuses?: readonly number[];
}
export async function fetchWithRetry(
url: string,
init?: RequestInit,
options?: FetchWithRetryOptions
): Promise<Response> {
const {
retries = 2,
timeout = 8000,
retryStatuses = [408, 429, 500, 502, 503, 504],
} = options ?? {};
let lastError: Error | null = null;
for (let attempt = 0; attempt <= retries; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
…init,
signal: controller.signal,
});
if (response.ok || !retryStatuses.includes(response.status)) {
return response;
}
// 状态码可重试但尚未达到最大次数,等待后重试
if (attempt < retries) {
await sleep(Math.pow(2, attempt) * 100); // 指数退避: 100ms, 200ms, 400ms
} else {
throw new Error(
`请求失败(状态码 ${response.status}),已重试 ${retries} 次: ${url}`
);
}
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
} finally {
clearTimeout(timer);
}
}
throw lastError ?? new Error(`请求超时或未知错误: ${url}`);
}
/** 平台无关的简单延时函数 */
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/** 跨平台兼容的 KV 存储接口(适配 Workers KV / Vercel KV / 内存) */
export interface KVStore {
get(key: string): Promise<string | null>;
set(key: string, value: string, ttlSeconds?: number): Promise<void>;
delete(key: string): Promise<void>;
}
/**
* 创建 KV 存储实例(自动适配运行时)
* Cloudflare Workers: 使用 Workers KV
* Vercel Edge: 使用 @vercel/kv
* 其他环境: 使用内存 Map(仅调试用)
*/
export function createKVStore(namespace?: string): KVStore {
const runtime = detectRuntime();
if (runtime === 'cloudflare-workers') {
// Cloudflare Workers 中全局 KV namespace 通过 bindings 注入
// 此处为类型声明,实际绑定由 wrangler.toml 配置
const kvNamespace = (globalThis as Record<string, unknown>)[namespace ?? 'KV'] as
| { get(key: string): Promise<string | null>; put(key: string, value: string, options?: { expirationTtl?: number }): Promise<void>; delete(key: string): Promise<void> }
| undefined;
if (!kvNamespace) {
throw new Error(
`Cloudflare Workers 环境中未找到 KV 绑定 "${namespace ?? 'KV'}",请检查 wrangler.toml`
);
}
return {
async get(key: string): Promise<string | null> {
try {
return await kvNamespace.get(key);
} catch (err) {
console.error(`[KV Store] 读取失败 key="${key}":`, err);
return null;
}
},
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
await kvNamespace.put(key, value, ttlSeconds ? { expirationTtl: ttlSeconds } : undefined);
},
async delete(key: string): Promise<void> {
await kvNamespace.delete(key);
},
};
}
// 兜底:内存存储(非生产环境使用)
console.warn('[KV Store] 使用内存存储(非生产环境),数据在实例销毁后丢失');
const store = new Map<string, { value: string; expiresAt?: number }>();
return {
async get(key: string): Promise<string | null> {
const entry = store.get(key);
if (!entry) return null;
if (entry.expiresAt && Date.now() > entry.expiresAt) {
store.delete(key);
return null;
}
return entry.value;
},
async set(key: string, value: string, ttlSeconds?: number): Promise<void> {
store.set(key, {
value,
expiresAt: ttlSeconds ? Date.now() + ttlSeconds * 1000 : undefined,
});
},
async delete(key: string): Promise<void> {
store.delete(key);
},
};
}
三、实际场景的性能对比
在相同负载下(API BFF 聚合接口,汇聚 3 个后端微服务数据),对比两个平台的性能数据:
| P50 冷启动 | 3.2ms | 48ms |
| P99 冷启动 | 6.8ms | 210ms |
| P50 响应时间(热) | 12ms | 18ms |
| 全球节点数 | 330+ | ~20(区域) |
| 免费额度 | 10 万请求/天 | 100 万请求/月(含带宽 100GB) |
| 最大执行时间 | 30s(付费) | 30s(Pro)/ 60s(Enterprise) |
| CPU 时间限制 | 30s/请求 | 无独立限制 |
关键差异在于冷启动与边缘覆盖。Workers 的 V8 Isolate 模型在冷启动上领先一个数量级;但 Vercel 与 Next.js 的深度整合在开发体验和生态上有明显优势。
四、选型决策框架
综合实践复盘,总结以下决策框架:
决策要点:
- 如果项目基于 Next.js 且需要兼容现有 npm 依赖,Vercel Edge Functions 是更直接的选择。
- 如果核心诉求是全球低延迟、API 代理、安全网关等轻量任务,Cloudflare Workers 的 V8 Isolate 模型优势更明显。
- 如果两个平台都需要覆盖,通过适配层抽象运行时差异(如上述 runtime-adapter.ts)可以降低迁移成本。
五、总结
Cloudflare Workers 与 Vercel Edge Functions 代表了边缘计算的两条不同路径:前者追求极致的性能与全球覆盖,后者注重开发生态与框架整合。
在实际项目中的经验是:不要为了 Edge 而 Edge。如果应用的核心用户集中在单一区域(如国内),中心化的 Serverless 方案(如阿里云函数计算 FC)在延迟上并无劣势,且生态更成熟。边缘计算的价值在全球化场景中才能充分体现。此外,两者均有执行时间上限,不适合长任务处理——对于超过 30 秒的操作(如视频转码),仍应回归传统的异步任务队列方案。


