Skip to main content

Next.js Edge Functions for Global Performance

7 min read
Speed up my Next.js globally

We move geo-routing, A/B tests and personalization into Next.js middleware on the Edge Runtime.

Leverage Next.js Edge Runtime and middleware for sub-50ms global response times with geo-routing, A/B testing, and personalized content delivery.

Why Edge Computing Matters

Traditional server-side rendering runs in a single region. A user in Sydney requesting a page from a server in Virginia adds 200-300ms of network latency before any processing begins. Edge functions execute in data centers closest to the user, eliminating this round-trip penalty.

Next.js Edge Runtime runs a subset of Node.js APIs on V8 isolates at the edge, enabling sub-50ms response times globally. This guide covers practical patterns for using edge functions in production, from the perspective of teams focused on server optimization.

Edge Runtime vs Node.js Runtime

Understanding the tradeoffs is critical before choosing where to run your code:

Edge Runtime: Cold starts under 5ms, runs in 30+ global locations, limited API surface (no filesystem, no native Node modules), 128MB memory limit, 25ms CPU time limit per request.

Node.js Runtime: Full Node.js API access, runs in a single region, cold starts of 250ms+, unlimited CPU time, up to 1GB memory.

Use Edge for lightweight, latency-sensitive operations. Use Node.js for heavy computation, database queries, and operations requiring full Node.js APIs.

Middleware for Global Routing

Next.js middleware runs on every request before routing. On the Edge Runtime, it executes at the nearest edge location:

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

export function middleware(request: NextRequest) {
  const country = request.geo?.country || "US";
  const response = NextResponse.next();

  // Set geo header for downstream components
  response.headers.set("x-user-country", country);

  // Redirect to country-specific store
  if (request.nextUrl.pathname === "/shop") {
    const storeMap: Record<string, string> = {
      DE: "/shop/eu",
      FR: "/shop/eu",
      GB: "/shop/uk",
      JP: "/shop/asia",
    };
    const targetPath = storeMap[country];
    if (targetPath) {
      return NextResponse.redirect(new URL(targetPath, request.url));
    }
  }

  return response;
}

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

Edge API Routes

Create API routes that run at the edge for latency-critical endpoints:

// app/api/feature-flags/route.ts
import { NextRequest, NextResponse } from "next/server";

export const runtime = "edge";

const flags: Record<string, { enabled: boolean; rollout: number }> = {
  new_checkout: { enabled: true, rollout: 0.25 },
  dark_mode: { enabled: true, rollout: 1.0 },
  ai_search: { enabled: false, rollout: 0 },
};

export async function GET(request: NextRequest) {
  const userId = request.headers.get("x-user-id") || "anonymous";

  const userFlags = Object.fromEntries(
    Object.entries(flags).map(([key, config]) => {
      if (!config.enabled) return [key, false];
      const hash = simpleHash(userId + key);
      return [key, hash < config.rollout];
    })
  );

  return NextResponse.json(userFlags, {
    headers: {
      "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
    },
  });
}

function simpleHash(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = (hash << 5) - hash + str.charCodeAt(i);
    hash |= 0;
  }
  return Math.abs(hash) / 2147483647;
}

A/B Testing at the Edge

Run A/B tests without client-side flicker by assigning variants at the edge:

// middleware.ts (A/B testing section)
function handleABTest(request: NextRequest): NextResponse {
  const experiment = "pricing-page-v2";
  const cookieName = `ab-${experiment}`;

  let variant = request.cookies.get(cookieName)?.value;

  if (!variant) {
    variant = Math.random() < 0.5 ? "control" : "treatment";
  }

  const url = request.nextUrl.clone();
  if (url.pathname === "/pricing" && variant === "treatment") {
    url.pathname = "/pricing-v2";
  }

  const response = NextResponse.rewrite(url);
  response.cookies.set(cookieName, variant, {
    maxAge: 60 * 60 * 24 * 30,
    httpOnly: true,
    sameSite: "lax",
  });

  return response;
}

This approach rewrites the URL internally - the user sees /pricing in their browser while the server renders the variant page. No layout shift, no flicker, no client-side JavaScript required.

Edge-Optimized Authentication

Validate JWT tokens at the edge to protect routes without a round-trip to your auth server:

// lib/edge-auth.ts
export async function verifyToken(token: string): Promise<boolean> {
  try {
    const [headerB64, payloadB64, signatureB64] = token.split(".");
    const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
    const signature = base64UrlDecode(signatureB64);

    const key = await crypto.subtle.importKey(
      "jwk",
      PUBLIC_JWK,
      { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
      false,
      ["verify"]
    );

    const valid = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", key, signature, data);
    if (!valid) return false;

    const payload = JSON.parse(atob(payloadB64));
    return payload.exp > Date.now() / 1000;
  } catch {
    return false;
  }
}

Performance Monitoring

Track edge function performance with custom headers:

export function middleware(request: NextRequest) {
  const start = Date.now();
  const response = NextResponse.next();
  response.headers.set("x-edge-latency", `${Date.now() - start}ms`);
  response.headers.set("x-edge-region", process.env.VERCEL_REGION || "unknown");
  return response;
}

Best Practices

  • Keep edge functions under 1MB after bundling - large bundles increase cold start times
  • Use Cache-Control headers aggressively for edge API responses
  • Avoid importing heavy libraries - prefer Web API equivalents (crypto.subtle over jsonwebtoken)
  • Test with realistic latency using tools like tc or Chrome DevTools network throttling
  • Monitor edge function execution times and error rates in your observability platform
  • Combine edge functions with a robust infrastructure setup for a complete performance strategy

Edge computing shifts latency-sensitive logic closer to users, delivering measurable improvements in Time to First Byte and overall user experience.

Frequently asked questions

What are the limits of the Next.js Edge Runtime?

The Next.js Edge Runtime has a 128MB memory limit and a 25ms CPU time limit per request, with no filesystem access and no native Node modules. Only a subset of Node.js APIs is available, running on V8 isolates. In return, cold starts stay under 5ms across 30+ global locations.

Should I use the Edge Runtime or the Node.js runtime in Next.js?

Pick by workload. Lightweight, latency-sensitive logic belongs on the Edge Runtime, while heavy computation, database queries and anything needing the full Node.js API belong on the Node.js runtime. The Node.js runtime runs in a single region with cold starts of 250ms or more, but it offers unlimited CPU time and up to 1GB of memory.

How do I redirect visitors to a country-specific store with Next.js middleware?

Read the visitor's country from the request's geo data in middleware.ts, map country codes to paths, for example DE and FR to /shop/eu, GB to /shop/uk and JP to /shop/asia, and return NextResponse.redirect for requests to /shop. The middleware can also set an x-user-country header for downstream components. A matcher that excludes _next/static, _next/image and favicon.ico keeps the middleware off static assets.

How do I run an A/B test in Next.js without client-side flicker?

Assign the variant in middleware at the edge. A cookie named after the experiment, such as ab-pricing-page-v2, stores a 50/50 random choice between control and treatment for 30 days, and NextResponse.rewrite serves /pricing-v2 to the treatment group. The browser still shows /pricing, so there is no layout shift, no flicker and no client-side JavaScript required.

How do I make a Next.js API route run on the Edge Runtime?

Add export const runtime = "edge" to the route file, for example app/api/feature-flags/route.ts. Edge API routes suit latency-critical endpoints such as feature flags, and they should send Cache-Control headers, for example public, s-maxage=60, stale-while-revalidate=300, so the edge can cache the response.

Can I verify JWTs in Next.js edge middleware?

Yes, with the Web Crypto API. Import the public key with crypto.subtle.importKey as a JWK using RSASSA-PKCS1-v1_5 with SHA-256, check the signature with crypto.subtle.verify, then confirm the exp claim is still in the future. Protected routes then skip a round-trip to the auth server, and crypto.subtle is preferred over a heavier library such as jsonwebtoken.

How much latency do Next.js edge functions save for users far from the server?

A user in Sydney requesting a page from a server in Virginia pays 200 to 300ms of network latency before any processing begins. Edge functions run in the data center closest to the user and remove that round trip, which makes sub-50ms response times possible globally. The improvement shows up in Time to First Byte and overall user experience.

How do I keep Next.js edge functions fast?

Keep each edge function under 1MB after bundling, because large bundles increase cold start times. Track latency by setting x-edge-latency and x-edge-region headers in middleware, with the region read from VERCEL_REGION, and monitor execution times and error rates. Test under realistic latency with tc or Chrome DevTools network throttling.

Read how we handle it in Cloud & Server Management.