Use Cases (Actualizado: 2/6/2026)

Vercel Edge Functions con Claude Code: guía práctica

Usa Vercel Edge Runtime con Claude Code: Middleware, webhooks firmados, A/B tests, cache y errores reales de producción.

Vercel Edge Functions con Claude Code: guía práctica

No elijas Edge solo porque suena rápido

Vercel Edge Functions ejecuta JavaScript en Edge Runtime. No es un proceso Node.js normal, sino un entorno pequeño basado en Web APIs como fetch, Request, Response, URL, TextEncoder y Web Crypto. Dicho de forma simple: es una puerta ligera antes de que la petición llegue a una página o a una API más pesada. Puede mirar URL, cabeceras, cookies y un cuerpo pequeño, y tomar una decisión rápida.

Claude Code encaja bien porque una implementación real de Edge casi nunca toca un único archivo. Un redireccionamiento por país afecta a middleware.ts, cabeceras de Vercel y pruebas en preview. Un A/B test toca cookies, cabeceras internas, analytics y rollback. Un webhook firmado toca raw body, firma HMAC, variables de entorno, límite de tamaño y reenvío a un servicio interno. Por eso conviene pedirle a Claude Code que revise el límite de runtime, no solo que genere código.

En junio de 2026, la documentación oficial de Vercel Edge Runtime no presenta Edge como una solución universal. Explica APIs soportadas, límites, regiones y casos donde Node.js sigue siendo mejor por rendimiento o fiabilidad. La documentación de Next.js sobre Middleware y Route Handlers también usa el modelo Web Request y Response. La regla práctica es clara: pon en Edge decisiones pequeñas de entrada y deja el trabajo duradero en servicios más fáciles de operar.

Para ampliar el tema, combina esta guía con Claude Code webhook implementation y Claude Code performance optimization. Así separas mejor firma, reintentos, cache y medición.

Cinco usos reales

Edge funciona mejor cuando la respuesta sale de metadatos de la petición o de un payload pequeño firmado. No es el lugar adecuado para grandes dependencias, transacciones largas de base de datos, conexiones TCP privadas, subidas grandes o streams largos de LLM.

CasoPor qué encaja en EdgeQué dejar en Node.js o backend
Redirección por paísPuede usar x-vercel-ip-country antes de renderizarPreferencias persistidas, precios, reglas de cuenta
A/B testUna cookie fija el bucket y se pasa como cabeceraAgregación, análisis estadístico, decisión de rollout
Autenticación ligeraRechaza preview o webhooks inválidos tempranoSesiones, roles, auditoría
Preparación de cacheNormaliza URL y query para estabilizar cache keysRevalidación, inventario, recomputación cara
Recepción de webhookVerifica un cuerpo pequeño y lo reenvíaPago final, emails, reintentos, CRM

Esta tabla también sirve como prompt para Claude Code. Especifica qué parte vive en Edge y qué parte no. Así evitas que el código generado importe APIs exclusivas de Node.js, conecte directamente con la base de datos o registre secretos durante el debug.

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"]

El diagrama muestra a Edge como una capa de entrada, no como el backend completo. Middleware clasifica la petición y añade metadatos. El Route Handler verifica un webhook pequeño. Las operaciones duraderas, con reintentos y efectos secundarios, pasan a una API interna, cola o worker.

Middleware de Next.js listo para copiar

Este middleware.ts incluye redirección por país, bucket de A/B test, una puerta ligera para preview y cabeceras de seguridad. Usa cabeceras de Vercel en lugar de request.geo para reducir diferencias entre versiones de Next.js. En local normalmente no tendrás x-vercel-ip-country, así que prueba esa parte en 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;
}

La muestra tiene límites intencionales. El A/B test solo asigna bucket; no decide un ganador. La puerta de preview no sustituye un sistema de autenticación. La redirección por país solo corre en /, evitando bucles. Middleware puede afectar a muchas rutas, así que cualquier complejidad innecesaria se paga rápido.

Route Handler Edge para webhooks firmados

El siguiente app/api/webhooks/provider/route.ts verifica una firma HMAC. HMAC significa que emisor y receptor comparten un secreto y calculan una firma sobre el cuerpo original. En Edge Runtime usamos Web Crypto y TextEncoder, no crypto.createHmac ni 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 });
}

El orden evita fallos sutiles: limitar tamaño, leer raw body, verificar firma, parsear JSON y reenviar. No conviertas este handler en el procesador de pagos completo; la idempotencia y los reintentos pertenecen al servicio interno.

Prompt de revisión y prueba mínima

El prompt siguiente obliga a Claude Code a revisar restricciones de runtime, no solo estilo:

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.

Para pruebas locales, Node.js puede generar la firma porque ese helper no se ejecuta dentro de 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

Después verifica en Preview Deployment las cabeceras de país, cookies HTTPS, bucles de redirección, logs y supuestos de región. vercel dev ayuda, pero no reproduce todo el entorno de Vercel.

Errores frecuentes

El primer error es usar APIs de Node.js sin darte cuenta. fs, Buffer, crypto.createHmac, módulos nativos y clientes de base de datos basados en TCP no pertenecen a Edge. A veces entran por dependencias indirectas, así que revisa el árbol de imports.

El segundo error es conectar la base de datos desde Edge. Si la base está en una sola región, cada petición viajará allí de todos modos y puedes aumentar la presión de conexiones. Para estado duradero, usa una API HTTP, una cola o una función Node.js cerca de la base.

El tercer error es malinterpretar cold start y region. Edge reduce la latencia de decisión en la entrada, pero no acerca datos remotos. preferredRegion ayuda, aunque debes medir y mirar logs.

El cuarto error es filtrar secretos. Body de webhook, firma, cookies, Authorization y secretos de preview no deben imprimirse. Si necesitas logs, enmascara primero.

El quinto error es ignorar body size y streaming. Edge va bien para peticiones pequeñas. Subidas grandes, CSV, imágenes y streams largos de LLM pertenecen a otra infraestructura.

El sexto error es confiar solo en local. vercel dev no replica por completo cabeceras de país, regiones reales, logs de preview ni cookies secure.

CTA de ClaudeCodeLab

En un proyecto individual, los ejemplos anteriores bastan para un prototipo. En equipo, lo difícil es acordar qué archivos pueden usar Edge Runtime, qué APIs están prohibidas, cómo se nombran variables de entorno y quién valida Preview Deployments.

ClaudeCodeLab ayuda a convertir eso en reglas de Claude Code, CLAUDE.md, prompts de revisión, recibos de verificación de Webhook y checks de Vercel. Si necesitas aplicarlo a un repositorio real, empieza por la página de Claude Code training and consultation. La meta no es añadir burocracia, sino evitar que un cambio pequeño en middleware afecte a todo el sitio.

Resultado al probarlo

Al aplicar esta estructura, la mejora principal fue la claridad. Middleware quedó limitado a redirección, bucket, cabeceras y bloqueo ligero. El Route Handler validó un webhook pequeño y lo reenvió. El prompt de revisión ayudó a detectar Buffer, JSON parseado antes de verificar raw body, cabeceras de Vercel ausentes en local y logs demasiado detallados. Edge Functions no son magia, pero funcionan bien cuando la frontera de petición se mantiene pequeña y verificable.

#Claude Code #Vercel #Edge Functions #edge computing #serverless
Gratis

PDF gratis: cheatsheet de Claude Code

Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.

Cuidamos tus datos y no enviamos spam.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.