Use Cases (更新: 2026/6/2)

用 Claude Code 安全实践 Vercel Edge Functions

用 Claude Code 实践 Vercel Edge Runtime:Middleware、签名验证、A/B测试、缓存前处理与常见坑。

用 Claude Code 安全实践 Vercel Edge Functions

不要只因为“Edge听起来更快”就使用它

Vercel Edge Functions 运行在 Edge Runtime 中。它不是普通的 Node.js 进程,而是一个以 Web API 为中心的轻量运行环境,可以使用 fetchRequestResponseURLTextEncoder 和 Web Crypto。换成更直白的话说,Edge Runtime 像是请求进入应用前的一道小门卫:它适合读取 URL、Header、Cookie 和很小的请求体,然后快速做出判断。

这也是 Claude Code 有价值的地方。真实项目中的 Edge 改动通常不是一个文件就结束。国家或地区跳转会碰到 middleware.ts、Vercel 请求 Header 和本地开发差异。A/B 测试会碰到 Cookie、请求 Header、统计事件和回滚。Webhook 会碰到原始请求体、签名、环境变量、请求体大小限制和后端队列。让 Claude Code 只“生成一个 Edge Function”并不够,应该让它同时审查运行时边界、日志、测试和生产环境差异。

截至 2026 年 6 月,Vercel 的 Edge Runtime 官方文档并没有把 Edge 描述成万能加速器。文档说明了可用 API、限制、区域、执行时间,也提醒部分工作负载更适合 Node.js。Next.js 文档中的 MiddlewareRoute Handlers 也围绕 Web Request 与 Response API 组织。实务结论是:把小而明确的入口判断放到 Edge,把持久化、重试、复杂业务逻辑留给后端服务。

如果你还在整理 Webhook 的重试和幂等性,可以继续看 Claude Code Webhook 实现指南。如果重点是整体速度和缓存策略,可以配合 Claude Code 性能优化指南 阅读。

五个适合 Edge 的实际场景

适合 Edge 的任务通常可以从请求元数据或很小的签名正文中得到答案。需要大量依赖、数据库长事务、私有网络连接、大文件上传或长时间 LLM 流式输出的任务,不应放在 Edge 中。

场景为什么适合 Edge应保留在 Node.js 或后端服务中
国家或地区跳转可根据 x-vercel-ip-country 等 Header 在入口处快速分流用户偏好保存、价格规则、账号策略
A/B 测试可用 Cookie 固定实验分组,并在渲染前写入请求 Header数据统计、显著性判断、实验停止决策
轻量认证或签名检查可提前拒绝非法预览请求或 Webhook会话签发、角色权限、审计日志
缓存前处理可规范化 URL 和查询参数,稳定缓存键重新生成缓存、库存更新、复杂计算
Webhook 接收可校验小正文签名并转发给内部服务支付确认、邮件发送、重试和 CRM 更新

这张表也适合直接放进 Claude Code 的提示词。你要明确告诉它哪些逻辑属于 Edge,哪些必须留在后端。这样可以减少生成代码误用 Node-only API、直接连接数据库、或者把 secret 打进日志的风险。

flowchart LR
  A["User request"] --> B["Next.js Middleware"]
  B --> C{"Small decision"}
  C --> D["Country redirect"]
  C --> E["A/B bucket"]
  C --> F["Light auth"]
  B --> G["Edge Route Handler"]
  G --> H["HMAC signature check"]
  H --> I["Internal API or queue"]

图中的 Edge 不是完整后端,而是入口控制层。Middleware 负责分类请求,Route Handler 负责验证小型 Webhook,真正需要持久化和重试的工作交给内部 API、队列或工作流服务。

可复制的 Next.js Middleware

下面的 middleware.ts 同时处理地区跳转、A/B 分组、预览页的轻量认证和安全 Header。示例使用 Vercel Header,而不是依赖 request.geo,这样更容易在不同 Next.js 版本中保持稳定。注意,本地开发通常没有 x-vercel-ip-country,地区跳转需要在 Vercel Preview Deployment 上确认。

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

const PUBLIC_FILE = /\.(?:png|jpg|jpeg|gif|svg|webp|ico|css|js|map|txt)$/i;
const SECRET_HEADER = "x-edge-shared-secret";

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

function chooseBucket(request: NextRequest): "a" | "b" {
  const current = request.cookies.get("ab_bucket")?.value;
  if (current === "a" || current === "b") return current;

  const random = new Uint8Array(1);
  crypto.getRandomValues(random);
  return random[0] < 128 ? "a" : "b";
}

function localeFromCountry(country: string | null): string | null {
  switch (country?.toUpperCase()) {
    case "JP":
      return "ja";
    case "KR":
      return "ko";
    case "CN":
    case "TW":
    case "HK":
      return "zh";
    case "BR":
      return "pt";
    case "ES":
    case "MX":
      return "es";
    default:
      return null;
  }
}

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

  if (PUBLIC_FILE.test(pathname)) {
    return NextResponse.next();
  }

  if (pathname === "/") {
    const country = request.headers.get("x-vercel-ip-country");
    const locale = localeFromCountry(country);
    if (locale) {
      return NextResponse.redirect(new URL(`/${locale}/`, request.url), 307);
    }
  }

  if (pathname.startsWith("/beta")) {
    const bucket = chooseBucket(request);
    const requestHeaders = new Headers(request.headers);
    requestHeaders.set("x-ab-bucket", bucket);

    const response = NextResponse.next({
      request: { headers: requestHeaders },
    });

    if (!request.cookies.has("ab_bucket")) {
      response.cookies.set("ab_bucket", bucket, {
        maxAge: 60 * 60 * 24 * 30,
        path: "/",
        sameSite: "lax",
        secure: request.nextUrl.protocol === "https:",
      });
    }

    return response;
  }

  if (pathname.startsWith("/preview")) {
    const expected = process.env.EDGE_SHARED_SECRET;
    const actual = request.headers.get(SECRET_HEADER);
    if (!expected || actual !== expected) {
      return NextResponse.redirect(new URL("/login", request.url), 307);
    }
  }

  const response = NextResponse.next();
  response.headers.set("x-content-type-options", "nosniff");
  response.headers.set("referrer-policy", "strict-origin-when-cross-origin");
  return response;
}

这个例子故意保持克制。A/B 测试只负责分组,不负责统计。预览认证只做入口拦截,不替代完整登录系统。地区跳转只在首页执行,避免把所有路径都卷进重定向循环。Middleware 可能影响大量请求,因此越小越容易审查。

Edge Route Handler 中验证 Webhook 签名

下面是 app/api/webhooks/provider/route.ts 示例。HMAC 可以理解为“发送方和接收方共享一个 secret,用它和原始正文算出签名,以确认正文没有被篡改”。Edge Runtime 中不使用 Node.js 的 crypto.createHmacBuffer,而是用 Web Crypto 和 TextEncoder

// app/api/webhooks/provider/route.ts
export const runtime = "edge";
export const preferredRegion = ["iad1", "hnd1"];

const MAX_BODY_BYTES = 256_000;

function hexToBytes(hex: string): Uint8Array {
  const clean = hex.replace(/^sha256=/, "").trim();
  if (!/^[0-9a-f]+$/i.test(clean) || clean.length % 2 !== 0) {
    return new Uint8Array();
  }

  const bytes = new Uint8Array(clean.length / 2);
  for (let index = 0; index < clean.length; index += 2) {
    bytes[index / 2] = Number.parseInt(clean.slice(index, index + 2), 16);
  }
  return bytes;
}

async function hmacSha256(secret: string, payload: string): Promise<Uint8Array> {
  const encoder = new TextEncoder();
  const key = await crypto.subtle.importKey(
    "raw",
    encoder.encode(secret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
  return new Uint8Array(signature);
}

function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean {
  if (a.length !== b.length) return false;

  let diff = 0;
  for (let index = 0; index < a.length; index += 1) {
    diff |= a[index] ^ b[index];
  }
  return diff === 0;
}

export async function POST(request: Request) {
  const secret = process.env.WEBHOOK_SECRET;
  const internalOrigin = process.env.INTERNAL_API_ORIGIN;
  const internalToken = process.env.INTERNAL_API_TOKEN;

  if (!secret || !internalOrigin || !internalToken) {
    return Response.json({ error: "server is not configured" }, { status: 500 });
  }

  const contentLength = Number(request.headers.get("content-length") ?? "0");
  if (contentLength > MAX_BODY_BYTES) {
    return Response.json({ error: "payload too large" }, { status: 413 });
  }

  const rawBody = await request.text();
  const rawBodyBytes = new TextEncoder().encode(rawBody);
  if (rawBodyBytes.byteLength > MAX_BODY_BYTES) {
    return Response.json({ error: "payload too large" }, { status: 413 });
  }

  const provided = hexToBytes(request.headers.get("x-signature-sha256") ?? "");
  const expected = await hmacSha256(secret, rawBody);
  if (!constantTimeEqual(provided, expected)) {
    return Response.json({ error: "invalid signature" }, { status: 401 });
  }

  const event = JSON.parse(rawBody) as { id?: string; type?: string };
  if (!event.id || !event.type) {
    return Response.json({ error: "invalid event" }, { status: 400 });
  }

  await fetch(`${internalOrigin}/api/webhook-events`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${internalToken}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({
      id: event.id,
      type: event.type,
      receivedAt: new Date().toISOString(),
    }),
  });

  return Response.json({ ok: true });
}

顺序很重要:先限制大小,再读取原始正文,再验证签名,最后才解析 JSON。验证后也不要把支付、发邮件、CRM 更新都放在 Edge 里;把事件转发给内部系统,让后端负责幂等、重试和审计。

给 Claude Code 的审查指令和最小测试

可以把下面的提示词直接交给 Claude Code。重点不是让它多写代码,而是让它按 Edge Runtime 的限制进行代码审查。

Review this Next.js Edge implementation.

Scope:
- middleware.ts
- app/api/webhooks/provider/route.ts
- related tests and environment variable names

Check:
- no Node-only APIs such as fs, net, tls, Buffer, or node:crypto in Edge files
- no direct database connection from Edge Runtime
- country redirect does not loop
- A/B bucket is stable by cookie and not written on every request
- webhook verifies the raw body before JSON parsing
- secrets, signatures, cookies, and authorization headers are not logged
- body size and production-only Vercel headers are documented

Return blockers first, then suggested tests.

本地测试中可以用 Node.js 生成 HMAC 签名,因为那只是测试辅助代码,不在 Edge Runtime 中运行。

npm run lint
npm run build
vercel dev

BODY='{"id":"evt_123","type":"checkout.completed"}'
SIG=$(node -e "const crypto=require('crypto'); const body=process.argv[1]; console.log('sha256='+crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(body).digest('hex'))" "$BODY")

curl -i http://localhost:3000/api/webhooks/provider \
  -X POST \
  -H "content-type: application/json" \
  -H "x-signature-sha256: $SIG" \
  --data "$BODY"

curl -I http://localhost:3000/beta
curl -I http://localhost:3000/preview

生产前还要在 Preview Deployment 中确认地区 Header、HTTPS Cookie、重定向、日志和区域假设。本地测试能发现语法和大部分路由问题,但不能完全模拟 Vercel 的边缘环境。

常见坑

第一个坑是误用 Node.js API。fsBuffercrypto.createHmac、原生模块和基于 TCP 的数据库客户端都不适合 Edge 文件。有时不是你直接导入,而是某个工具库间接导入,所以要让 Claude Code 检查依赖路径。

第二个坑是从 Edge 直接连接数据库。即使代码在用户附近运行,如果每次都要回到同一个数据库区域,延迟仍然存在,还可能增加连接压力。需要持久状态时,优先走 HTTP API、队列或靠近数据库的 Node.js Function。

第三个坑是误解 cold start 和 region。Edge 可以降低入口判断的延迟,但不能把远端数据库变成本地数据库。preferredRegion 有帮助,但必须用日志和指标确认实际路径。

第四个坑是 secret 和日志泄露。Webhook 正文、签名、Cookie、Authorization Header、预览 secret 都不应该原样打印。调试日志可以存在,但要先脱敏。

第五个坑是请求体大小和 streaming。Edge 适合小请求和快速判断,不适合大文件上传、CSV 导入、图片处理或长时间 LLM 流式输出。读取 request.text() 时一定要设大小上限。

第六个坑是本地与生产差异。vercel dev 很有用,但它不能完整模拟地区 Header、实际运行区域、Preview 日志和 secure Cookie 行为。测试应分为本地、构建、Preview 三层。

ClaudeCodeLab 的团队落地方式

个人项目可以从上面的两个文件开始。团队项目更难的是规则:哪些文件允许使用 Edge Runtime,哪些 API 禁止,环境变量如何命名,Preview Deployment 由谁确认,Claude Code 生成的代码如何审查。

ClaudeCodeLab 可以帮助团队整理 Claude Code 规则、CLAUDE.md、Edge Runtime 审查清单、Webhook 验证记录和 Vercel 部署检查。如果你希望把这些模式应用到真实仓库,可以从 Claude Code 培训与咨询 开始。目标不是增加流程,而是避免一个小小的 middleware 改动影响整个站点。

实际尝试后的结果

按照本文的结构实践后,最大的收益不是“代码变得更快”,而是边界更清楚。Middleware 只负责跳转、A/B 分组、Header 和轻量拦截;Edge Route Handler 只验证小型签名 Webhook 并转发事件。给 Claude Code 明确审查指令后,它更容易发现 Buffer 混入、签名前先解析 JSON、Vercel 专有 Header 本地不可用、日志过度暴露等问题。Edge Functions 不是魔法加速器,但把入口判断保持小而可测时,它们是非常实用的生产工具。

#Claude Code #Vercel #Edge Functions #edge computing #serverless
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。