Edge functions, redirects and rewrites

Redirects and rewrites at the edge, the difference between rewriting and proxying, edge runtime limits, geo and header-based routing, and simple A/B routing.

Redirects and rewrites

DirectiveWhat the client seesURL changesUse it for
Redirect 301A redirect responseYesA permanent move
Redirect 302 or 307A redirect responseYesA temporary or context-dependent move
RewriteThe final content directlyNoFriendly URLs, legacy paths, a new backend
ProxyThe final content directlyNoServing another origin under your hostname
Custom responseWhatever you returnNoMaintenance pages, AB routing, auth gates
# edge rules, expressed declaratively where the platform supports it
[[redirects]]
  from = "/old-guide/*"
  to = "/guides/:splat"
  status = 301
  force = true

[[rewrites]]
  from = "/api/*"
  to = "https://api.internal.example/:splat"
  status = 200

[[headers]]
  for = "/static/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"
  • Redirect chains waste a round trip each hop. Emit a single 301 from the edge to the final URL.
  • Rewrites are invisible to the client, so absolute URLs generated by the application must still use the public hostname. Set the forwarded Host and X-Forwarded-Proto headers correctly.
  • Never redirect to a URL that itself redirects. Audit the map after every change.

Edge functions and their limits

// an edge function: runs per request, close to the user
export default async function handler(request) {
  const url = new URL(request.url);

  // block a path before it reaches the origin
  if (url.pathname.startsWith("/admin") && !request.headers.get("cookie")?.includes("session=")) {
    return new Response("Not found", { status: 404 });
  }

  // route by country, with a sane fallback when the header is absent
  const country = request.headers.get("cf-ipcountry") ?? "US";
  if (url.pathname === "/" && ["DE", "FR", "NL"].includes(country)) {
    return Response.redirect(new URL("/eu/", url), 302);
  }

  return fetch(request);      // pass through to the origin
}
LimitTypical valueConsequence
CPU time per request10-50 msNo heavy parsing or hashing
Wall-clock timeoutShort at the edge, longer at a regional edgeNo slow upstream calls
Memory128 MBNo large in-memory datasets
Bundle size1-5 MBFew dependencies
Node APIsPartial or absentNot every library runs there
SubrequestsA small number per requestChaining several APIs will fail
CachingA KV store with eventual consistencyNot a database, not immediately consistent

An edge function is not a server. It runs on the request path, it must finish quickly, and every millisecond it spends is added to every user's latency. Use it for routing, headers, redirects and small decisions - not for business logic that belongs behind a queue.

Routing and simple experiments

// a stable A/B assignment: hash the visitor id, not the request
async function assignVariant(request, env) {
  const cookie = parseCookies(request.headers.get("cookie") ?? "");
  let visitor = cookie.ab_id;

  if (!visitor) {
    visitor = crypto.randomUUID();
  }

  // hashing makes the assignment stable for the same visitor
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(visitor));
  const bucket = new Uint8Array(digest)[0] % 100;

  const variant = bucket < 10 ? "b" : "a";        // 10 percent to the variant

  const response = await fetch(request);
  const headers = new Headers(response.headers);
  headers.append("Set-Cookie", "ab_id=" + visitor + "; Path=/; Max-Age=2592000; SameSite=Lax");
  headers.set("x-ab-variant", variant);
  return new Response(response.body, { status: response.status, headers });
}
  1. Assign the variant from a stable identifier so a user does not flip between versions on every request.
  2. Keep the cache key independent of the variant, or you will cache the same URL twice and lose the traffic mixing.
  3. Do not split a cached response at the edge by mutating the body; that defeats the cache entirely. Split the HTML into a cached shell and a small variant fragment.
  4. Never experiment on a checkout or a payment path without a way to force a single variant for a support case.
  5. Record the variant on every event so the analysis is possible afterwards.
💡
Every edge rule is production code with the shortest feedback loop in your stack - it applies to every request immediately. Keep the rule set small, review changes like code, and always have a way to disable a rule in one action.

FAQ

Rewrite or proxy?
They are the same mechanism viewed from different sides: the client URL stays the same and the content comes from elsewhere. Choose based on the platform's naming and whether the origin needs the original host header.
Can I run a database query in an edge function?
Technically yes with an HTTP-based database API, but each query adds latency on the request path and connection pooling is difficult. Prefer a regional function or a cache.

Edge architecture: personalisation and A/B testing at the edge How a CDN serves your content

Last refreshed 2026-09-18.