Suggest an editImprove this articleRefine the answer for “Edge runtime vs Node.js runtime in Next.js”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Edge runtime** in Next.js runs server code in V8 isolates at 200+ global CDN locations with ~40ms cold starts but no filesystem or Node.js built-ins. **Node.js runtime** (default) has full API access including `fs`, native modules, and any npm package, at ~400ms cold start. ```tsx export const runtime = 'edge'; // opt-in export async function GET() { return Response.json({ ok: true }); // runs globally in ~40ms } ``` **Key point:** Edge for stateless low-latency ops like auth and redirects; Node.js for database access and file operations.Shown above the full answer for quick recall.Answer (EN)Image**Edge runtime** runs Next.js server code in V8 isolates distributed across 200+ CDN locations worldwide. **Node.js runtime** (the default) gives full access to the Node.js ecosystem: `fs`, native modules, persistent connections, and any npm package. ## Theory ### TL;DR - Node.js is a full kitchen: `fs`, `crypto`, `net`, native modules, full heap - Edge is a vending machine at every airport: limited tools, instant from anywhere - Core difference: Node.js uses V8 + libuv event loop; Edge uses V8 isolates with Web APIs only - Cold start: Edge ~40ms vs Node.js ~400ms (Vercel benchmark data) - Decision rule: Edge for auth and redirects; Node.js for database access and heavy compute ### Quick example ```tsx // Node.js runtime (default) - full API access import fs from 'fs'; export async function GET() { const data = fs.readFileSync('./products.json', 'utf-8'); return Response.json(JSON.parse(data)); // reads from local disk } // Edge runtime - Web APIs only export const runtime = 'edge'; export async function GET() { const res = await fetch('https://api.example.com/products'); return Response.json(await res.json()); // ~40ms at the nearest PoP } ``` Node.js reads from disk. Edge cannot touch the filesystem, so you fetch from a remote source instead. The tradeoff is direct: full capabilities vs global latency. ### Key difference Node.js runtime is a standard server process: V8 plus libuv's I/O loop. It opens sockets, reads files, and runs native add-ons. Edge runtime compiles your code into V8 isolates, removes everything that needs an OS-level syscall, and deploys copies to CDN points of presence globally. You lose filesystem access and Node.js built-ins. You get global distribution and cold starts near 40ms instead of 400ms. ### When to use - JWT validation, auth checks, redirects -> Edge (stateless, runs close to the user, no DB roundtrip needed) - Geo-based routing, A/B testing -> Edge (`request.geo` is available natively in middleware only) - Database queries with Prisma or Drizzle -> Node.js (native drivers do not run in isolates) - File processing, image manipulation -> Node.js (needs `fs` and native bindings) - Long compute jobs (>1s) -> Node.js (Edge caps at 30s CPU and ~1MB memory) - Cold-start-sensitive stateless paths -> Edge; persistent connection pools -> Node.js ### Comparison table | Feature | Node.js Runtime | Edge Runtime | |---|---|---| | **APIs** | Full Node.js (`fs`, `crypto`, `net`) | Web standards (Fetch, URLSearchParams, `crypto.subtle`) | | **Where it runs** | Single region or self-hosted | 200+ PoPs globally (V8 isolates) | | **Cold start** | ~400ms | ~40ms | | **CPU limit** | 300s | 30s | | **Memory** | Full heap | ~1MB | | **Filesystem** | Yes (`fs` module) | No | | **Native modules** | Yes | No | | **npm packages** | All | Web API-compatible only | | **Streaming** | Yes | Yes (up to 4MB in Next.js 15) | | **State** | Persistent (connections reused) | Stateless only | | **Best for** | DB access, file ops, heavy compute | Auth, redirects, personalization | ### How it works internally Next.js compiles Edge routes into V8 isolate bundles using esbuild. There is no libuv I/O loop involved. Any import of a Node.js built-in fails at build time (`next build`), not at runtime. When deployed to Vercel, Edge bundles run in a Cloudflare Workers-compatible environment that polyfills some missing Web APIs but rejects `fs`, `path`, `net`, and similar modules entirely. Middleware in Next.js always runs on Edge. That is not configurable. ### Common mistakes **Importing `fs` in an Edge route:** ```tsx // Build error: "Module not found: Can't resolve 'fs'" export const runtime = 'edge'; import fs from 'fs'; // fails at next build // Fix: fetch from a CDN or remote API const res = await fetch('https://my-cdn.com/data.json'); const data = await res.text(); ``` **Large environment variables over 4KB:** Edge serializes env vars to request headers. Secrets over 4KB get truncated without any warning in development. The bug only appears in production. ```tsx // Truncated in production, no error thrown const bigKey = process.env.HUGE_PRIVATE_KEY; // Fix: use Vercel Edge Config or an external secrets API const config = await fetch('https://edge-config.vercel.com/...'); ``` **Porting Express-style handlers directly:** ```tsx // Works in Node.js, returns 400 in Edge export default (req, res) => res.json({ body: req.body }); // Fix: Edge uses the Web Request API export async function POST(request: Request) { const body = await request.json(); return Response.json({ body }); } ``` **Crypto API incompatibility between runtimes:** Edge uses `crypto.subtle` (Web Crypto API). Node.js traditionally uses `crypto.createCipheriv`. If you write Edge crypto code with `crypto.subtle` and copy it to a Node.js handler, it works fine on Node.js 18+ (which ships `crypto.subtle` globally). The reverse is never safe: `createCipheriv` in an Edge route fails at build time. **Hitting the 1MB memory cap in loops:** V8 isolates kill the request when memory runs out. No clear error message appears. If you process large payloads, stream them: `for await (const chunk of stream)` instead of buffering the full response in memory. ### Real-world usage - Vercel Speed Insights: Edge runtime for geo-based sampling across regions - Stripe and Supabase: Edge middleware for JWT validation (no database roundtrip needed) - Shopify Hydrogen: Edge routes for cart personalization - Linear.app: Edge API routes for webhook fan-out - T3 Stack (`create-t3-app`): auth middleware runs on Edge by default ### Follow-up questions **Q:** Which Node.js APIs are missing in Edge runtime? **A:** `fs`, `path`, `net`, `zlib`, `child_process`, and most Node built-ins. Replace them with Fetch, URLSearchParams, and `crypto.subtle`. **Q:** How does choosing Edge affect the build process? **A:** esbuild removes Node.js polyfills and fails the build if any imported module depends on a Node built-in. You find these problems at `next build`, not in production. **Q:** What are the actual cold start numbers? **A:** Vercel benchmarks put Edge at ~40ms and Node.js at ~400ms. The gap comes from isolate startup cost being near-zero vs spinning up a Node process. **Q:** Any Edge limitations worth knowing in Next.js 15? **A:** Streaming in Edge routes is capped at 4MB. React's `cache()` function does not work in Edge handlers either. **Q (senior):** You want to rate-limit requests by IP using Redis in Edge middleware. What is the problem and how do you solve it? **A:** Edge is stateless. It cannot hold a persistent TCP connection to Redis. The fix is Upstash Redis, which wraps Redis commands in HTTP calls so Edge can reach it via Fetch. You pay one HTTP roundtrip per request, so you need to factor that latency in and consider caching rate-limit state with TTL headers. ## Examples ### Edge vs Node.js for the same data route ```tsx // app/api/node/route.ts - Node.js runtime (default) import fs from 'fs'; import path from 'path'; export async function GET() { const filePath = path.join(process.cwd(), 'data', 'products.json'); const raw = fs.readFileSync(filePath, 'utf-8'); return Response.json(JSON.parse(raw)); // Reads from disk, runs in one region, ~400ms cold start } ``` ```tsx // app/api/edge/route.ts - Edge runtime export const runtime = 'edge'; export async function GET() { const res = await fetch('https://api.example.com/products'); return Response.json(await res.json()); // No filesystem, runs at 200+ PoPs, ~40ms cold start } ``` Same endpoint, different constraints. Pick Node.js when data lives on the server. Pick Edge when the upstream API is remote and your users are globally distributed. ### Production middleware: geo redirect and bot blocking ```tsx // middleware.ts - always runs on Edge, not configurable import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const country = request.geo?.country; const ua = request.headers.get('user-agent') ?? ''; // Block bots in the US before they reach the origin if (country === 'US' && ua.toLowerCase().includes('bot')) { return NextResponse.redirect(new URL('/blocked', request.url)); } if (country === 'UA') { return NextResponse.rewrite(new URL('/ua', request.url)); } return NextResponse.next(); } export const config = { matcher: ['/((?!_next|favicon.ico).*)'], }; // Pattern from vercel/commerce - blocks bots in <20ms globally ``` `request.geo` is Edge-only. It is not available in Node.js API routes. This middleware executes before the page renders, at the CDN layer, without touching the origin server at all. ### Crypto migration between runtimes ```tsx // app/api/encrypt/route.ts - written for Edge export const runtime = 'edge'; export async function POST(request: Request) { // Web Crypto API - works on Edge and on Node.js 18+ const key = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); const iv = crypto.getRandomValues(new Uint8Array(12)); const payload = await request.arrayBuffer(); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, key, payload ); return new Response(encrypted); } // If someone ports this to Node.js using the old API: // import { createCipheriv, randomBytes } from 'crypto'; // That version BREAKS on Edge at build time. // Write with crypto.subtle. It runs everywhere. ``` Write encryption logic with `crypto.subtle`. It runs on Edge and on Node.js 18+. I've seen this exact problem in teams copying Stripe webhook handlers from an older Node.js service into Edge middleware: build fails because the original code used `createCipheriv`, which Edge has never supported.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.