Vercel Edge Functions 深度指南:Edge Runtime vs Node.js、流式响应与中间件实战

系统讲解 Vercel Edge Functions 的完整能力:Edge Runtime 与 Node.js Runtime 架构差异、流式响应实现、A/B 测试路由、地理定位、性能优化与冷启动策略。含 Next.js/SvelteKit/vanilla 三种实现方式与实战代码。

Vercel Edge Functions 是平台近年来最核心的差异化能力之一:基于 V8 Isolate 技术的 Edge Runtime 让代码在全球 100+ 边缘节点就近执行,延迟降到 50ms 以内,冷启动接近零。对于 AI 流式响应、全球化路由、A/B 测试、访问控制等场景,Edge Functions 几乎是无可替代的选择。本文从底层架构到生产代码,全面拆解 Edge Functions 的能力边界。


一、Edge Runtime 与 Node.js Runtime:核心区别

1.1 架构差异

Node.js Runtime(Serverless Functions)
  └── 完整 Node.js 容器
      ├── 完整 fs / net / crypto / buffer API
      ├── npm 生态(Prisma、sharp、bcrypt 等)
      ├── 冷启动:50-500ms
      └── 执行限制:10s(Hobby)/ 60s(Pro)

Edge Runtime(Edge Functions)
  └── V8 Isolate(轻量沙盒)
      ├── 类浏览器的 Web API(fetch / Request / Response)
      ├── TypedArray / TextEncoder / Web Crypto API
      ├── ❌ 无 fs、无 Node net 模块、受限 npm 包
      ├── 冷启动:< 5ms
      └── 执行限制:30s 默认(可配置到 60s)

1.2 对比总表

维度Node.js RuntimeEdge Runtime
底层引擎Node.js 完整容器V8 Isolate(Chrome 同款引擎)
启动时间50-500ms 冷启动< 5ms 冷启动
内存限制1024MB / 3008MB(取决于区域)128MB
执行时间上限10s / 60s30s / 60s(可配置)
文件系统(fs)✅ 完整支持❌ 不可用
TCP 连接(Postgres)✅ 直接连接❌ 需通过 HTTP 代理(如 Prisma Accelerate)
Node.js 内置模块✅ 全部⚠️ 有限子集(util、buffer 等部分可用)
npm 生态✅ 99% 可用⚠️ 仅纯 JS / Web API 兼容包
Web API❌ 需要 polyfill✅ fetch / Request / Response / Headers / URL 原生支持
流式响应(Streaming)✅ 支持✅ 天然设计支持,体验更优
全球部署按区域(us-east-1 等)全网点(Anycast)
适用场景数据库操作、文件处理、复杂后端逻辑路由、认证、流式 AI、轻量 API、边缘聚合

1.3 什么时候选什么?

需要做数据库操作?
  ├── 是 → 需要 Prisma/Postgres 直连?
  │       ├── 是 → Node.js Runtime(Serverless Functions)
  │       └── 否(可用 HTTP API)→ Edge Runtime ✅
  └── 否 → 请求延迟敏感?(如 AI 流式输出)
          ├── 是 → Edge Runtime ✅
          └── 否(批量处理、定时任务)→ Node.js Runtime

结论:大部分场景可以混合使用——页面渲染/数据库操作走 Node.js,中间件/轻量 API/流式响应走 Edge。


二、Next.js 中声明 Edge Runtime

2.1 Route Handler(API 路由)

// app/api/stream/route.ts
// 顶部声明 runtime
export const runtime = 'edge';

export async function GET(request: Request) {
  const encoder = new TextEncoder();

  const stream = new ReadableStream({
    start(controller) {
      let count = 0;
      const interval = setInterval(() => {
        controller.enqueue(encoder.encode(`data: Message ${++count}\n\n`));
        if (count >= 5) {
          clearInterval(interval);
          controller.close();
        }
      }, 1000);
    },
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

2.2 页面级别

// app/dashboard/page.tsx
export const runtime = 'edge'; // SSR 页面在 Edge 渲染

export default async function DashboardPage() {
  // Edge Runtime 下不能直连 Postgres
  // 但可以通过外部 API 获取数据
  const data = await fetch('https://api.example.com/stats').then(r => r.json());

  return (
    <main>
      <h1>Dashboard</h1>
      <p>{data.totalUsers} users</p>
    </main>
  );
}

2.3 Middleware(天然 Edge)

Next.js Middleware 在 Vercel 上始终运行在 Edge Runtime(不能选择 Node.js):

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Edge Runtime 执行:低延迟、全球节点
  const country = request.geo?.country || 'US';

  // A/B 测试:按国家路由到不同页面
  if (country === 'CN' && request.nextUrl.pathname === '/') {
    return NextResponse.rewrite(new URL('/cn-home', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

三、流式响应(Streaming):AI 场景核心能力

3.1 OpenAI SDK 流式示例

// app/api/chat/route.ts
export const runtime = 'edge';

export async function POST(request: Request) {
  const { messages } = await request.json();

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: 'gpt-4',
      stream: true,
      messages,
    }),
  });

  // 直接透传 OpenAI 的 SSE 流
  return new Response(response.body, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
    },
  });
}

前端消费 SSE:

// lib/chat.ts
export async function* streamChat(messages: any[]) {
  const response = await fetch('/api/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages }),
  });

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value);
    // 解析 SSE 格式:data: {...}\n\n
    for (const line of chunk.split('\n')) {
      if (line.startsWith('data: ')) {
        const json = line.slice(6);
        if (json === '[DONE]') return;
        try {
          yield JSON.parse(json);
        } catch { /* 偶现的 JSON 片段 */ }
      }
    }
  }
}

3.2 Vercel AI SDK 方式(推荐)

npm install ai openai
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export const runtime = 'edge';

export async function POST(request: Request) {
  const { messages } = await request.json();

  const result = streamText({
    model: openai('gpt-4'), // 或 'gpt-3.5-turbo'
    messages,
    maxTokens: 1024,
  });

  return result.toDataStreamResponse();
}

前端配合 useChat hook(Vercel AI SDK 提供):

// app/page.tsx
'use client';
import { useChat } from 'ai/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div>
      {messages.map(m => (
        <div key={m.id}>
          <strong>{m.role}:</strong> {m.content}
        </div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
        <button>Send</button>
      </form>
    </div>
  );
}

Edge Functions 做流式的优势

  • 用户请求被路由到最近边缘节点 → 到 OpenAI API 的延迟最小化
  • 流式返回时,Edge 层持续保持连接,不像 Serverless 有硬超时
  • 全球用户都能获得低延迟的 “打字机” 效果

四、地理位置与个性化

4.1 获取用户地理位置

Edge Functions 可以读取请求来源的地理信息:

// app/api/geo/route.ts
export const runtime = 'edge';

export function GET(request: Request) {
  // request.geo 仅在 Next.js Middleware 和 Edge Functions 中可用
  // 在独立 Edge Functions 中,使用 cf-connecting-ip 和 Cloudflare 的 GeoIP

  const country = request.headers.get('cf-ipcountry') || request.headers.get('x-vercel-ip-country') || 'unknown';
  const city = request.headers.get('x-vercel-ip-city') || 'unknown';
  const region = request.headers.get('x-vercel-ip-country-region') || 'unknown';
  const latitude = request.headers.get('x-vercel-ip-latitude');
  const longitude = request.headers.get('x-vercel-ip-longitude');

  return Response.json({
    country,
    city,
    region,
    location: latitude && longitude ? { lat: latitude, lng: longitude } : null,
  });
}

可用的请求头(Vercel Edge):

Header说明
x-vercel-ip-countryISO 3166-1 alpha-2 国家代码
x-vercel-ip-country-region州/省代码
x-vercel-ip-city城市名(英文)
x-vercel-ip-latitude纬度
x-vercel-ip-longitude经度
x-vercel-ip-timezone时区

4.2 多语言路由

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

const locales = ['en', 'zh', 'ja'];
const defaultLocale = 'en';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 检查路径已有语言前缀
  const pathnameHasLocale = locales.some(
    locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );
  if (pathnameHasLocale) return NextResponse.next();

  // 根据用户国家判断语言
  const country = request.geo?.country || 'US';
  const locale =
    country === 'CN' || country === 'TW' || country === 'HK' ? 'zh' :
    country === 'JP' ? 'ja' :
    defaultLocale;

  request.nextUrl.pathname = `/${locale}${pathname}`;
  return NextResponse.redirect(request.nextUrl);
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

五、身份验证与访问控制

5.1 JWT 验证(Edge 兼容)

// lib/auth-edge.ts
import { SignJWT, jwtVerify } from 'jose';

const secret = new TextEncoder().encode(process.env.JWT_SECRET!);

export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, secret, { clockTolerance: 60 });
    return payload;
  } catch {
    return null;
  }
}

⚠️ jsonwebtoken 包依赖 Node.js 的 crypto 模块,在 Edge Runtime 不可用。使用 jose 包,它基于 Web Crypto API。

5.2 Edge Middleware 做认证拦截

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from './lib/auth-edge';

const publicPaths = ['/login', '/register', '/api/auth'];

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  if (publicPaths.some(p => pathname.startsWith(p))) {
    return NextResponse.next();
  }

  const token = request.cookies.get('token')?.value;
  const payload = token ? await verifyToken(token) : null;

  if (!payload) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  // 把用户信息注入请求头(下游页面/API 可用)
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-user-id', payload.sub as string);
  requestHeaders.set('x-user-role', payload.role as string);

  return NextResponse.next({
    request: { headers: requestHeaders },
  });
}

六、性能优化

6.1 减少冷启动

Edge Functions 冷启动已极低(< 5ms),但仍有优化空间:

// 避免在全局作用域做重计算
// ❌ 不推荐
const heavyData = JSON.parse(
  fs.readFileSync('./data.json', 'utf8') // Edge 不能这么做
);

// ✅ 推荐:延迟加载
const getData = () => {
  if (!globalThis.__data) {
    globalThis.__data = fetch('https://cdn.example.com/data.json').then(r => r.json());
  }
  return globalThis.__data;
};

export default async function handler(request: Request) {
  const data = await getData();
  return Response.json(data);
}

6.2 缓存策略

Edge Functions 可以设置 CDN 缓存头,减少重复执行:

export const runtime = 'edge';

export async function GET() {
  const data = await fetchSomeData();

  return Response.json(data, {
    headers: {
      'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=86400',
    },
  });
}
  • s-maxage=300:CDN 缓存 5 分钟
  • stale-while-revalidate=86400:缓存过期后,旧数据继续服务,后台异步更新

6.3 Bundle 大小优化

Edge Functions 的 Bundle 越小,启动越快:

# 分析 Edge Functions 的 Bundle 大小
npm install -D @vercel/nft

# 或查看 Vercel Dashboard → Network → 查看 Functions 的 Compressed Size

优化建议:

  • 避免引入整个 lodash(用 lodash-es 的按需导入)
  • jose 替代 jsonwebtoken
  • 用原生 fetch 替代 axios
  • 移除未使用的依赖

七、Vanilla Edge Functions(非 Next.js)

如果你不用 Next.js,Vercel 也支持独立的 Edge Functions:

// api/hello.ts
export const config = {
  runtime: 'edge',
};

export default async function handler(request: Request) {
  return new Response('Hello from Edge!', {
    status: 200,
    headers: { 'Content-Type': 'text/plain' },
  });
}
// api/uppercase.ts
export const config = { runtime: 'edge' };

export default async function handler(request: Request) {
  const { text } = await request.json();
  return Response.json({ result: text.toUpperCase() });
}

八、常见问题(FAQ)

Edge Runtime 能连 Redis 吗?

不能直连 TCP。Redis 协议基于 TCP,Edge Runtime 不支持原生 TCP Socket。解决方法:

  1. 使用 Redis HTTP API(如 Upstash Redis 提供的 REST API)
  2. 使用 Durable Objects / KV(Cloudflare 生态)
  3. 把缓存逻辑放到 Node.js Serverless Functions 中

Edge Functions 和 Vercel Middleware 有什么区别?

  • Middleware:请求过滤层,决定"要不要继续处理这个请求",可修改请求头、重定向、重写 URL
  • Edge Functions:独立 API 端点或页面渲染,执行业务逻辑,返回响应
  • 关系:Middleware 执行在前,Edge Functions 执行在后。一个请求可能先经过 Middleware,再到达 Edge Function。

为什么我的 Edge Function 提示 “Module not found”?

因为 Edge Runtime 只支持纯 JavaScript / Web API 兼容的 npm 包。检查:

  1. 是否有包依赖 Node.js 原生模块(fs、net、child_process)
  2. 是否使用了 jsonwebtoken(改用 jose
  3. 是否使用了 Prisma 直连(改用 Prisma Accelerate)

Edge Functions 在国内表现如何?

Edge Functions 的部署节点分布在日本、新加坡、香港等地,对中国大陆用户的延迟约 50-100ms。如果需要更低延迟,建议:

  1. 配合 Cloudflare Proxy(将域名 DNS 托管到 Cloudflare)
  2. 考虑 Cloudflare Workers(在中国大陆有更多 PoP 节点)

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「saas」更多文章