Edge Runtime Fundamentals & Platform Constraints

Edge runtimes execute JavaScript in lightweight V8 isolates deployed at Points of Presence (PoPs) worldwide. The critical difference from traditional serverless is the execution environment: no file system, no raw TCP sockets, no Node.js built-ins—only a curated subset of browser-compatible Web APIs. Every request is stateless, every resource threshold is a hard limit enforced at the JavaScript engine level (not the OS level), and exceeding those limits terminates execution immediately with a 502 or 504 response.

This constraint-first model eliminates container cold-start overhead but forces architectural discipline. The guides in this section assume you are designing around platform limits, not abstracting them away. They cover the full surface of edge constraints: the supported Web API matrix, the Vercel Edge versus Cloudflare Workers trade-off, cold-start mitigation, memory and CPU limits, polyfill strategies for Node.js APIs, and bundle optimization. Once the runtime is understood, the companion domains on middleware chain architecture and edge caching and CDN integration build composable request pipelines and cache layers on top of it.

Edge runtime constraint map A V8 isolate at a Point of Presence is bounded by memory, CPU, bundle size, and Web-API constraints, sitting between the client and origin or edge state. Client Point of Presence V8 isolate stateless handler 128 MB mem CPU budget bundle cap Web APIs only Origin Edge state KV / DO / Cache
Every edge handler runs inside a memory-, CPU-, and bundle-bounded V8 isolate exposing only Web APIs, mediating between the client, origin, and edge state stores.

Execution Model

The edge request lifecycle runs in under 5 ms for pure routing logic:

  1. DNS resolves to the nearest PoP.
  2. A reverse proxy intercepts the HTTP request.
  3. A pre-warmed V8 isolate evaluates the handler.
  4. The isolate returns a Response object; the connection is finalized.

Isolates share physical hardware across tenants but maintain strict memory and CPU boundaries enforced by the V8 engine. No shared state leaks between requests. Global variables reset between isolate invocations unless the platform explicitly reuses a warm isolate (Cloudflare Workers does this; Vercel does not guarantee it).

The provider-agnostic handler signature:

export type EdgeMiddleware = (
  req: Request,
  ctx: ExecutionContext
) => Promise<Response | void>;

export function createRouter(middlewares: EdgeMiddleware[]) {
  return async (req: Request, ctx: ExecutionContext) => {
    for (const middleware of middlewares) {
      const result = await middleware(req, ctx);
      if (result instanceof Response) return result;
    }
    return new Response('Not Found', { status: 404 });
  };
}

This enforces separation of concerns: routing logic runs at the edge; heavy computation or database transactions are deferred to regional or origin services.

Resource Boundaries

Provider Memory cap CPU per request Execution timeout
Cloudflare Workers 128 MB 10 ms (free) / 30 s default, up to 5 min (paid) synchronous CPU 30 s wall-clock
Vercel Edge Middleware 128 MB — (wall-clock limit applies) 1000 ms wall-clock
Netlify Edge Functions 512 MB 50 s wall-clock

CPU quotas apply to synchronous computation. I/O wait (outbound fetch, KV reads) does not count against the CPU budget on Cloudflare but does count against wall-clock time.

Design patterns that respect these ceilings:

export async function handleRequest(req: Request, ctx: ExecutionContext) {
  const MAX_PAYLOAD = 5 * 1024 * 1024; // 5 MB

  const contentLength = Number(req.headers.get('content-length') || 0);
  if (contentLength > MAX_PAYLOAD) {
    return new Response('Payload exceeds edge limit', { status: 413 });
  }

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 25_000);

  try {
    const response = await processRequest(req, { signal: controller.signal });
    return response;
  } catch (err) {
    if ((err as DOMException).name === 'AbortError') {
      return new Response('Execution timeout exceeded', { status: 504 });
    }
    ctx.waitUntil(logError(err));
    return new Response('Internal Server Error', { status: 500 });
  } finally {
    clearTimeout(timeoutId);
  }
}

ctx.waitUntil() schedules background work after the response is sent. Use it for logging and cache warming—never for work that must complete before the response.

Supported API Surface

Edge runtimes expose a WHATWG-compliant API surface:

  • Fetch stack: fetch, Request, Response, Headers, Body
  • URL handling: URL, URLSearchParams, URLPattern
  • Streams: ReadableStream, WritableStream, TransformStream
  • Crypto: crypto.subtle (WebCrypto), crypto.randomUUID()
  • Encoding: TextEncoder, TextDecoder, atob, btoa
  • Timers: setTimeout, clearTimeout, setInterval (within execution window)
  • Caching: caches.open() / Cache API (Cloudflare; limited on others)

Absent: fs, net, tls, child_process, path, Node.js crypto module, process.exit. See Supported Web APIs in Edge Runtimes for a full per-provider matrix.

Caching Architecture

Edge caching operates across two layers:

HTTP response cachingCache-Control, CDN-Cache-Control, and Surrogate-Key headers control how PoPs store and serve responses. Use stale-while-revalidate to serve stale content immediately while revalidating asynchronously.

KV / Durable Objects — programmatic key-value storage with different consistency guarantees:

Mechanism Latency Consistency Suitable For
HTTP Cache-Control < 10 ms Strong (per PoP) Static assets, public API responses
Edge KV (e.g., Cloudflare KV) 10–50 ms Eventually consistent Feature flags, session tokens
Durable Objects 5–20 ms Strongly consistent (single region) Rate limiting, real-time state

KV stores use eventual consistency across PoPs. Writes may take seconds to propagate globally. Use origin shielding for strongly consistent writes: route all mutations through a designated primary region while reads are served from the nearest edge cache.

JWT Verification at the Perimeter

Validating tokens at the edge eliminates round-trips to auth services. The jose library is tree-shakeable and works in all edge runtimes without Node.js polyfills:

import { createRemoteJWKSet, jwtVerify } from 'jose';

export async function authMiddleware(req: Request, ctx: ExecutionContext) {
  const authHeader = req.headers.get('authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return new Response('Unauthorized', { status: 401 });
  }

  const token = authHeader.slice(7);
  const JWKS = createRemoteJWKSet(new URL(process.env.AUTH_JWKS_URL!));

  try {
    const { payload } = await jwtVerify(token, JWKS, { algorithms: ['RS256'] });

    const headers = new Headers(req.headers);
    headers.set('X-User-ID', payload.sub as string);
    headers.set('X-User-Role', payload['role'] as string);

    return new Response(null, { status: 200, headers });
  } catch {
    return new Response('Invalid or expired token', { status: 403 });
  }
}

Observability

Telemetry at the edge must be asynchronous and sampled. Synchronous logging blocks the main thread and consumes CPU budget.

const SAMPLE_RATE = 0.1;

export function recordTrace(req: Request, ctx: ExecutionContext) {
  if (Math.random() > SAMPLE_RATE) return;

  const payload = {
    timestamp: Date.now(),
    method: req.method,
    path: new URL(req.url).pathname,
    rayId: req.headers.get('cf-ray') ?? 'unknown',
  };

  ctx.waitUntil(
    fetch(process.env.TELEMETRY_URL!, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    }).catch(() => {})
  );
}

Propagate traceparent and tracestate headers through all upstream fetch calls to maintain W3C Trace Context continuity. Never log request bodies or sensitive headers at the edge—log request IDs and route them to secure aggregation pipelines.

Deployment Checklist

  1. Type check & lint: tsc --noEmit with edge-specific ESLint rules (flag process, fs, net).
  2. Build & bundle: esbuild --bundle --minify --target=es2022 --format=esm --external:node:*
  3. Validate constraints: bundle size < provider limit (1 MB uncompressed for Cloudflare Workers), no synchronous I/O, WebCrypto only.
  4. Inject secrets: map environment variables to platform secret stores; never hardcode.
  5. Deploy & verify: health-check across 3+ PoPs.
  6. Monitor & roll back: alert on p95 latency > 200 ms or error rate > 1%; automate rollback.

For initialization latency patterns see Managing Cold Starts in Serverless Environments. For bridging Node.js dependencies see Polyfill Strategies for Node.js APIs at the Edge. For bundle sizing see Edge Bundle Optimization Techniques.

Conclusion

Edge runtimes are not general-purpose compute environments. They are routing, transformation, and cryptographic verification layers that operate under hard constraints. Every architectural decision—algorithm selection, dependency choice, caching strategy—must be evaluated against the memory ceiling and CPU budget of the target provider. Design for the constraint, not around it.

Frequently Asked Questions

What is the difference between an edge runtime and a serverless function?

An edge runtime executes JavaScript in a pre-warmed V8 isolate at a Point of Presence near the user, exposing only Web APIs with hard memory (128 MB) and CPU limits. A serverless function runs in a full Node.js, Python, or Go container in a single region, with OS access, larger payloads, and longer timeouts but a measurable cold start. Edge suits stateless routing, auth, and transformation; serverless suits heavy compute and direct database connections. See when to use edge versus serverless functions for API calls.

Why are Node.js modules like fs and crypto unavailable at the edge?

Edge isolates run a curated WHATWG-compliant runtime, not a Node.js process, so OS-level modules such as fs, net, tls, and the Node crypto module have no underlying system to bind to. Use crypto.subtle for cryptography, fetch for network I/O, and edge storage (KV, R2, Blobs) instead of a filesystem. For bridging dependencies that still expect Node built-ins, see the polyfill strategies guide.

How much memory and CPU do edge runtimes give me?

Cloudflare Workers and Vercel Edge cap memory at 128 MB per isolate; Netlify Edge Functions allow 512 MB. Cloudflare enforces a synchronous CPU budget (10 ms free, up to 30 s on paid plans) separate from wall-clock time, while Vercel and Netlify apply only a wall-clock limit. Full per-provider figures are in memory and CPU limits across edge providers.

Do edge functions have cold starts?

V8 isolates initialize in under 1 ms when warm because the engine is already running and the script is snapshotted, so edge cold starts are far smaller than container cold starts. They are not zero: a brand-new script, a large bundle, or expensive top-level initialization still adds latency. Keep top-level work minimal and bundles small; the cold-start management guide covers the measurement and mitigation workflow.

What is the maximum bundle size for an edge function?

Cloudflare Workers cap a script at 1 MB uncompressed on the free plan and up to 10 MB gzipped on paid plans; Vercel Edge allows roughly 1–4 MB compressed; Netlify Edge Functions allow 20 MB. Tree-shaking, ESM-only imports, and replacing Node built-ins shrink bundles below these caps — see edge bundle optimization techniques.