Supported Web APIs in Edge Runtimes

This guide is part of Edge Runtime Fundamentals & Platform Constraints.

Edge runtimes deliberately restrict the API surface to WHATWG-compliant browser primitives. The rationale is portability and determinism: by running on a curated subset of the same APIs browsers expose, isolates can be pre-compiled, snapshotted, and deployed globally without OS-level dependencies. What you gain in cold-start speed and geographic distribution, you pay for in API surface reduction. Because each platform builds its V8 isolate on a different base — V8 directly for Cloudflare and Vercel, Deno for Netlify — the available surface diverges in ways that break code which passes locally but fails in production.

This guide maps what is and is not available across the three major edge platforms, with accurate constraint figures as of mid-2026.

Edge Web API surface A shared WHATWG core of fetch, URL, streams, WebCrypto, and encoding sits inside the supported surface, while Node built-ins fall outside it and provider shims sit at the boundary. Supported edge surface (all three providers) fetch / Request / Response URL / URLSearchParams ReadableStream / Transform crypto.subtle / randomUUID TextEncoder / TextDecoder structuredClone / atob Shims: node:crypto (Vercel) / nodejs_compat (CF) fs / net / tls child_process / path Buffer / raw TCP Absent (Node only)
A shared WHATWG core is available everywhere; Node-only built-ins fall outside the surface, and per-provider shims bridge a narrow band at the boundary.

Universal API Surface

All major edge runtimes (Cloudflare Workers, Vercel Edge Middleware, Netlify Edge Functions) support:

API Notes
fetch, Request, Response, Headers WHATWG Fetch; streaming body via ReadableStream
URL, URLSearchParams Full WHATWG URL; URLPattern varies by platform
ReadableStream, WritableStream, TransformStream Web Streams API; backpressure supported
crypto.subtle WebCrypto: AES-GCM, HMAC, RSA, ECDSA, SHA family
crypto.randomUUID() Cryptographically secure
TextEncoder, TextDecoder UTF-8; TextDecoder supports additional encodings
atob, btoa Base64 encode/decode
setTimeout, clearTimeout Within the execution window only
performance.now() High-resolution timer; available everywhere
console.log/warn/error Routed to platform log aggregation
structuredClone() Deep copy; available in all modern runtimes

Absent everywhere: fs, net, tls, child_process, path, os, http (Node.js module), Buffer (Node.js), process.exit, synchronous I/O, raw TCP sockets, WebAssembly compilation at request time (pre-compiled WASM modules can be instantiated).

Provider-Specific Constraints

Runtime Constraint Thresholds

Constraint Cloudflare Workers Vercel Edge Middleware Netlify Edge Functions
Memory 128 MB 128 MB 512 MB
CPU budget 10 ms (free) / 30 s default, up to 5 min (paid) synchronous No separate CPU limit No separate CPU limit
Wall-clock timeout 30 s 1000 ms (middleware) 50 s
Bundle size 10 MB gzipped (paid) / 3 MB (free) 1 MB compressed 20 MB
Filesystem None (use KV / R2) None (use Vercel Blob) None (use Netlify Blobs)
WebCrypto Full: RSA, EC, AES-GCM, HMAC, SHA Full + limited node:crypto shim Full via Deno crypto
Node.js compat nodejs_compat flag (opt-in) Partial shim via @vercel/edge Deno + explicit import maps

Provider Callouts

Cloudflare Workers: Zero Node.js globals by default. Enable nodejs_compat in wrangler.toml to access Buffer, process, stream, and a subset of crypto. Database drivers must use HTTP-based or native bindings (D1, Hyperdrive). The Cache API (caches.default) is fully functional. URLPattern is supported.

Vercel Edge Middleware: Runs inside the Next.js edge runtime. cookies() and headers() helper functions from next/headers are available only in Server Components and Route Handlers, not in middleware.ts. In middleware.ts use req.cookies and direct header manipulation. The runtime exposes path and url from Node.js as partial shims; avoid fs and net even if they appear resolvable locally.

Netlify Edge Functions: Deno-based runtime. Imports use URL-style or npm: specifiers. The context.next() and context.rewrite() methods are Netlify-specific extensions. Automatic redirect handling in fetch is enabled by default. The Deno standard library is accessible but adds to bundle size.

Streaming Responses Without Buffering

import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  const externalRes = await fetch('https://api.example.com/data-stream');

  if (!externalRes.ok || !externalRes.body) {
    return new Response('Upstream failed', { status: 502 });
  }

  // Stream body directly; inject cache headers
  const headers = new Headers(externalRes.headers);
  headers.set('Cache-Control', 'public, max-age=300, stale-while-revalidate=600');
  headers.delete('Content-Length'); // Required when streaming

  return new Response(externalRes.body, {
    status: externalRes.status,
    headers,
  });
}

Never buffer an entire response body into memory when you can stream it. Buffering a 50 MB response on a platform with a 128 MB memory cap will trigger an OOM kill.

Cryptographic Operations

The Node.js crypto module is absent. Use crypto.subtle for all signing, verification, and hashing operations:

async function verifyPayloadSignature(
  payload: ArrayBuffer,
  signature: Uint8Array,
  key: CryptoKey
): Promise<boolean> {
  try {
    return await crypto.subtle.verify(
      { name: 'HMAC', hash: { name: 'SHA-256' } },
      key,
      signature,
      payload
    );
  } catch (err) {
    // Fail closed: abort on any verification error
    throw new Error('Signature verification failed', { cause: err });
  }
}

Import CryptoKey objects via crypto.subtle.importKey; do not pass raw strings as secrets. Use crypto.subtle.digest for hashing and crypto.subtle.sign/verify for HMAC and RSA operations.

Structured Logging & Correlation IDs

Edge environments typically strip stack traces. Use structured JSON with a correlation ID generated at request entry:

export async function middleware(request: Request) {
  const correlationId = crypto.randomUUID();
  const start = performance.now();

  try {
    const response = await handleRequest(request);
    console.log(JSON.stringify({
      level: 'info',
      correlationId,
      path: new URL(request.url).pathname,
      latencyMs: (performance.now() - start).toFixed(2),
      status: response.status,
    }));
    return response;
  } catch (err) {
    console.error(JSON.stringify({
      level: 'error',
      correlationId,
      message: err instanceof Error ? err.message : 'unknown',
    }));
    return new Response('Service Unavailable', { status: 503 });
  }
}

CI Compatibility Testing

Run a matrix against all target runtimes as part of CI. The most practical check is a static bundle audit that flags node: prefixed imports and globals (process, Buffer, require) that edge runtimes do not expose:

# Audit bundle for edge-incompatible imports
npx esbuild src/edge-handler.ts --bundle --analyze --outfile=/dev/null 2>&1 | grep -E "(node:|Buffer|process\.env)"

For per-provider deployment configuration and polyfill management, see Polyfill Strategies for Node.js APIs at the Edge and Managing Cold Starts in Serverless Environments.

Auditing API Compatibility Before Deploy

Run this workflow to confirm a handler stays inside the supported surface before it ships:

  1. Inventory the dependency tree. Resolve every transitive import and flag any package that pulls in node:-prefixed modules or references process, Buffer, or require.
  2. Map each flagged API to a Web equivalent. Replace crypto.createHash with crypto.subtle.digest, Buffer with Uint8Array + TextEncoder, and node-fetch with native fetch.
  3. Run a static bundle audit in CI. Use esbuild --analyze and grep for incompatible tokens so the build fails fast rather than at the first production request.
  4. Validate streaming paths. For any response over a few kilobytes, confirm the body is piped through ReadableStream rather than buffered into memory.
# Audit bundle for edge-incompatible imports
npx esbuild src/edge-handler.ts --bundle --analyze --outfile=/dev/null 2>&1 | grep -E "(node:|Buffer|process\.env)"

Runtime Compatibility Checklist

  • No node:
  • All hashing, signing, and verification use crypto.subtle, not the Node crypto
  • Responses larger than a few kilobytes stream via ReadableStream
  • URLPattern
  • Any Node compatibility shim (nodejs_compat, @vercel/edge

Frequently Asked Questions

Is the Node.js crypto module available in any edge runtime?

Not natively. The standards-based replacement is crypto.subtle (WebCrypto), available on all three providers for hashing, HMAC, RSA, ECDSA, and AES-GCM. Cloudflare’s nodejs_compat flag and Vercel’s partial node:crypto shim expose a subset of the Node API, but portable code should target WebCrypto directly. The polyfill strategies guide covers bridging the gap.

Why does my code work locally but fail when deployed to the edge?

Local development servers (vercel dev, wrangler dev without --remote) often run on a Node.js emulation layer that resolves modules the production isolate does not expose. A path or fs import can appear to work locally and then throw at the edge. Always run a static bundle audit and test against real infrastructure with wrangler dev --remote or a preview deployment.

Can I compile WebAssembly at request time in an edge function?

No. Runtime WASM compilation is disallowed because it would defeat the snapshotting that keeps isolates fast to start. Pre-compiled WASM modules can be instantiated, so compile WASM at build time and ship the binary as part of the bundle, mindful of the platform bundle-size cap.

Is URLPattern available everywhere?

URLPattern is supported on Cloudflare Workers and Deno-based Netlify Edge Functions, and is available in current Vercel Edge runtimes, but it is the most likely of the routing primitives to vary by version. Gate it behind a typeof URLPattern !== 'undefined' check, or fall back to URL plus manual segment matching for maximum portability.

How do I handle large request or response bodies within a 128 MB cap?

Stream them. Pass the upstream Response.body (a ReadableStream) straight through rather than calling .text() or .arrayBuffer(), which buffers the whole payload into memory. Delete the Content-Length header when forwarding a stream, and offload anything above ~1 MB to a serverless function.

Conclusion

The API surface restriction is the defining constraint of edge runtimes. Before writing any edge function, audit your dependency tree for Node.js built-ins, identify which operations require WebCrypto equivalents, and validate streaming compatibility for any payload above a few kilobytes. The payoff—sub-10 ms isolate initialization and global PoP distribution—is only realized when the code respects the boundaries the platform was built around.