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.
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:
- Inventory the dependency tree. Resolve every transitive import and flag any package that pulls in
node:-prefixed modules or referencesprocess,Buffer, orrequire. - Map each flagged API to a Web equivalent. Replace
crypto.createHashwithcrypto.subtle.digest,BufferwithUint8Array+TextEncoder, andnode-fetchwith nativefetch. - Run a static bundle audit in CI. Use
esbuild --analyzeand grep for incompatible tokens so the build fails fast rather than at the first production request. - Validate streaming paths. For any response over a few kilobytes, confirm the body is piped through
ReadableStreamrather 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 Nodecrypto - 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.