Vercel Edge Functions com Claude Code: guia prático
Use Vercel Edge Runtime com Claude Code: Middleware, webhooks assinados, testes A/B, cache e armadilhas de produção.
Não escolha Edge só porque parece mais rápido
Vercel Edge Functions executa JavaScript no Edge Runtime. Ele não é um processo Node.js comum, e sim um ambiente pequeno baseado em Web APIs como fetch, Request, Response, URL, TextEncoder e Web Crypto. Em termos simples, é uma porta leve antes da página ou da API. Ela lê URL, headers, cookies e um body pequeno, então toma uma decisão rápida.
Claude Code ajuda porque uma mudança real em Edge quase nunca fica em um único arquivo. Redirecionamento por país envolve middleware.ts, headers da Vercel e diferenças entre local e preview. A/B test envolve cookies, headers internos, analytics e rollback. Webhook assinado envolve raw body, HMAC, variáveis de ambiente, limite de tamanho e encaminhamento para um serviço interno. Por isso, peça ao Claude Code para revisar o limite do runtime, e não apenas gerar código.
Em junho de 2026, a documentação oficial do Vercel Edge Runtime descreve APIs disponíveis, limites, regiões e casos em que Node.js continua sendo melhor para performance e confiabilidade. A documentação do Next.js sobre Middleware e Route Handlers também usa o modelo Web Request e Response. A regra prática é: decisões pequenas de entrada ficam no Edge; trabalho durável fica no backend.
Para continuar, veja também Claude Code webhook implementation e Claude Code performance optimization. Esses artigos ajudam a separar assinatura, retry, idempotência, cache e medição.
Cinco casos de uso reais
Edge funciona melhor quando a resposta vem de metadados da requisição ou de um payload pequeno assinado. Não é ideal para dependências grandes, transações longas de banco, conexões de rede privadas, uploads grandes ou streams longos de LLM.
| Caso de uso | Por que combina com Edge | O que fica em Node.js ou backend |
|---|---|---|
| Redirecionamento por país | Usa x-vercel-ip-country antes da renderização | Preferências salvas, preços, regras de conta |
| A/B test | Cookie cria bucket estável antes da página | Agregação, análise estatística, decisão de rollout |
| Autenticação leve | Bloqueia preview ou webhook inválido cedo | Sessões, papéis, logs de auditoria |
| Pré-processamento de cache | Normaliza URL e query para cache keys estáveis | Revalidação, estoque, recomputação cara |
| Recebimento de webhook | Verifica body pequeno e encaminha evento | Pagamento final, emails, retries, CRM |
Essa tabela também serve como prompt para Claude Code. Deixe claro o que pode ficar no Edge e o que deve ficar fora. Assim você reduz imports Node-only, conexões diretas ao banco e logs com segredos.
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"]
O diagrama mostra Edge como camada de entrada, não como backend completo. Middleware classifica a requisição e adiciona metadados. O Route Handler verifica um webhook pequeno. A parte durável, com retries e efeitos colaterais, vai para API interna, fila ou worker.
Middleware Next.js copiável
Este middleware.ts inclui redirecionamento por país, bucket de A/B, uma barreira leve para preview e headers de segurança. Ele usa headers da Vercel em vez de request.geo, o que reduz diferenças entre versões do Next.js. Em local, x-vercel-ip-country normalmente não existe; valide essa parte no 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;
}
O exemplo é propositalmente limitado. O A/B test só define bucket; não escolhe vencedor. A proteção de preview não substitui autenticação. O redirecionamento por país roda apenas na raiz para evitar loop.
Route Handler Edge para webhook assinado
O arquivo app/api/webhooks/provider/route.ts abaixo verifica uma assinatura HMAC. HMAC significa que emissor e receptor compartilham um segredo e calculam uma assinatura sobre o body original. No Edge Runtime usamos Web Crypto e TextEncoder, não crypto.createHmac nem Buffer.
// 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 });
}
A ordem importa: limite tamanho, leia raw body, verifique a assinatura, faça parse do JSON e encaminhe. Pagamento final, email e retry devem ficar no serviço interno, onde idempotência é mais fácil.
Prompt de revisão e teste mínimo
Use este prompt para que Claude Code revise as restrições de 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.
No teste local, Node.js pode gerar a assinatura porque esse helper não roda dentro do Edge.
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
Também valide em Preview Deployment: headers de país, cookies HTTPS, loops de redirect, logs e hipóteses de região. vercel dev ajuda, mas não replica tudo.
Armadilhas comuns
A primeira armadilha é usar APIs de Node.js sem perceber. fs, Buffer, crypto.createHmac, módulos nativos e clientes TCP de banco não pertencem a arquivos Edge. Verifique imports indiretos.
A segunda é conectar o banco diretamente a partir do Edge. Se o banco está em uma região única, a requisição viajará até lá mesmo assim e ainda pode aumentar conexões. Prefira HTTP API, fila ou função Node.js perto do banco.
A terceira é entender mal cold start e region. Edge reduz latência na porta de entrada, mas não aproxima dados remotos. preferredRegion precisa ser confirmado com logs e métricas.
A quarta é vazar segredos em logs. Body de webhook, assinatura, cookies, Authorization e segredo de preview não devem aparecer em texto claro.
A quinta é ignorar body size e streaming. Edge serve para requests pequenos, não para upload grande, CSV, imagem ou stream longo de LLM.
A sexta é confiar apenas em local. vercel dev não reproduz completamente headers da Vercel, regiões reais, logs de preview e cookies secure.
CTA da ClaudeCodeLab
Em projeto individual, os exemplos já bastam para um protótipo. Em equipe, o desafio é regra: quais arquivos podem usar Edge Runtime, quais APIs são proibidas, como nomear variáveis de ambiente e quem valida Preview Deployment.
ClaudeCodeLab ajuda a transformar isso em regras de Claude Code, CLAUDE.md, prompts de revisão, recibos de verificação de webhook e checks de Vercel. Para adaptar ao seu repositório, comece pela página Claude Code training and consultation. A meta é evitar que uma pequena mudança de middleware cause problema em todo o site.
Resultado ao testar
Ao usar esta estrutura, o maior ganho foi clareza. Middleware ficou limitado a redirect, bucket, headers e bloqueio leve. O Route Handler verificou um webhook pequeno e encaminhou o evento. O prompt de revisão ajudou a encontrar Buffer, parse de JSON antes do raw body, headers da Vercel ausentes no local e logs detalhados demais. Edge Functions não são mágica, mas funcionam bem quando a fronteira de request é pequena e testável.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.
Regras de handoff para equipes com Claude Code: evidências, permissões, rollback e receita
Formato prático para entregar trabalho do Claude Code com prova, permissões, rollback, PDF grátis, Gumroad e consultoria.