Next.js Edge Runtime vs Node Runtime: Performance, Limits, and When to Use Each (2026)

Next.js Edge Runtime vs Node.js Runtime in 2026: cold starts, the 4 MB limit, which database drivers work on the Edge, and the exact rules I use to pick one per route.

Edge vs Node Runtime in Next.js 16 (2026)

Updated: June 18, 2026

The Next.js Edge Runtime is a lightweight V8-isolate-based execution environment that runs your code at Vercel's global edge network with sub-50ms cold starts, while the Node.js Runtime is the full Node.js process that supports the entire npm ecosystem at the cost of slower cold starts and regional deployment. In 2026, with Next.js 16's new Fluid Compute model, the choice is less binary than it used to be. But picking the wrong runtime still costs you latency, bundle size, or breaks your database driver outright. I've shipped both flavors in production (and burned a weekend on at least one cryptic Edge build failure), so this guide walks through exactly when to use each.

  • Edge Runtime uses V8 isolates with a ~4MB code-size limit and only Web Standard APIs. No fs, net, or native modules.
  • Node.js Runtime supports the full npm ecosystem (Prisma, Sharp, native drivers) but has 100–800ms cold starts on traditional serverless.
  • Middleware (proxy.ts in Next.js 16) defaults to the Edge Runtime; route handlers and pages default to Node.js Runtime.
  • Vercel's Fluid Compute (2025) closes the gap by keeping warm Node functions ready, reducing the cold-start argument for Edge.
  • Pick Edge for geographically distributed reads, auth checks, and streaming AI; pick Node for database writes with ORMs, image processing, and PDF generation.
  • You can mix runtimes per route using export const runtime = 'edge' or 'nodejs' at the file level.

What is the Edge Runtime in Next.js?

So, the Edge Runtime is a stripped-down JavaScript execution environment based on V8 isolates rather than full Node.js processes. Vercel popularized it by deploying these isolates across roughly 100 points of presence worldwide, so a request from Tokyo hits a Tokyo isolate instead of round-tripping to us-east-1. Cloudflare Workers, Deno Deploy, and Netlify Edge Functions use the same underlying primitive. In Next.js, the Edge Runtime is the default for middleware.ts (renamed to proxy.ts in Next.js 16) and is opt-in for route handlers and pages.

What you give up in exchange is the Node.js standard library. There's no fs, no child_process, no net. You get the Web Fetch API, Web Streams, Web Crypto (SubtleCrypto), URL, URLPattern, TextEncoder, and a curated subset of globals. Anything that reaches for process.versions.node at runtime fails, and many npm packages do this transitively, which is why edge builds reveal hidden Node dependencies that bundlers can't tree-shake away.

The official Next.js Edge Runtime reference lists every supported API. Bookmark it. You'll consult it more often than you expect.

Edge Runtime vs Node Runtime at a glance

Before getting into specifics, here's the practical comparison most teams actually need. The dimensions below are the ones I weigh on every new route: bundle limit, cold start, API surface, region behavior, and pricing model. Numbers reflect Vercel's published limits as of June 2026; self-hosted setups differ.

DimensionEdge RuntimeNode.js Runtime
Underlying engineV8 isolateFull Node.js 22 process
Code size limit (Vercel)4 MB after minification250 MB unzipped (50 MB on Hobby)
Cold start~10–50 ms100–800 ms (cold), <10 ms (Fluid warm)
Geographic distributionRuns at every Vercel PoP (~100 cities)One or more regions you select
Supported APIsWeb Standards only (fetch, Web Crypto, Streams)Full Node.js stdlib + npm
Native modulesNot supportedSupported (Sharp, node-postgres, etc.)
Streaming responseFirst-class via Web StreamsSupported via Web Streams or Node streams
Pricing model (Vercel)Per request + CPU msPer active CPU ms (Fluid Compute)
Best forAuth, A/B routing, AI streaming, geo-personalizationDB writes, image/PDF gen, long tasks, legacy SDKs

How to set the runtime per route

Next.js lets you opt a route handler, page, or layout into the Edge Runtime with a single export. The runtime is resolved at build time, not at request time, so the bundler can validate that your code doesn't touch Node-only APIs.

// app/api/geo/route.ts
export const runtime = 'edge'

export async function GET(request: Request) {
  const country = request.headers.get('x-vercel-ip-country') ?? 'US'
  return Response.json({ country, hint: `Cheapest plan in ${country}` })
}

For the Node.js Runtime, you can either omit the export (since Node is the default for route handlers in Next.js 14+) or set it explicitly to keep intent obvious to reviewers:

// app/api/upload/route.ts
import { writeFile } from 'node:fs/promises'

export const runtime = 'nodejs'
export const maxDuration = 60 // seconds, Pro plan and up

export async function POST(req: Request) {
  const buf = Buffer.from(await req.arrayBuffer())
  await writeFile('/tmp/upload.bin', buf) // works only on Node
  return Response.json({ ok: true })
}

Middleware is a special case. In Next.js 16 the file was renamed from middleware.ts to proxy.ts, and it still defaults to the Edge Runtime, but you can now opt in to Node by exporting runtime = 'nodejs' there too. For details on the rename and the breaking semantics, see our complete guide to Next.js middleware and proxy.ts.

What are the limitations of the Edge Runtime?

The Edge Runtime's constraints are the main reason teams revert to Node. The four that bite hardest in 2026:

1. No Node.js built-ins

No fs, path, os, child_process, worker_threads, or node:net. Use Web Crypto instead of node:crypto, the Web Streams API instead of node:stream, and HTTP via fetch rather than node:http.

2. Code size cap

Vercel enforces a 4 MB cap on the compiled, minified Edge bundle (1 MB on Hobby). That sounds generous until you import jose, zod, and a heavy validation schema in middleware that runs on every request. Use the Edge bundle analyzer (ANALYZE=true next build) and move large dependencies into route handlers instead.

3. CPU time limit

Edge functions on Vercel have a 30-second wall clock with a smaller "CPU active" budget, typically tens of milliseconds for synchronous work between awaits. Long-running JSON parsing, image transformations, or PDF generation will get killed.

4. Restricted dynamic code execution

You can't use eval or new Function. This breaks libraries that ship their own runtime (older versions of handlebars, some ajv compilation modes, and a surprising number of templating engines).

Cold starts and Fluid Compute in 2026

The traditional Edge-vs-Node debate centered on cold starts: a fresh Node Lambda might take 300–800 ms to spin up, while an Edge isolate boots in single-digit milliseconds. That argument lost a lot of its force in 2025 when Vercel rolled out Fluid Compute, a model that keeps a pool of warm Node containers handling many concurrent invocations on the same instance. In practice, p99 cold starts for Node routes on Fluid are now in the 30–80 ms range for small bundles.

What this means for the runtime decision in 2026:

  • Cold-start-driven choices are weaker. If your only reason for Edge was "Node cold starts hurt UX", Fluid Compute may have already fixed it.
  • Geographic distribution is still Edge's biggest moat. Fluid containers still run in the regions you select. A user in São Paulo hitting an Edge function gets a São Paulo isolate; hitting Fluid Node in iad1 means a transatlantic round trip.
  • Pricing changed shape. Fluid charges for active CPU, not wall clock, so an awaiting fetch costs almost nothing. This used to be Edge's pricing advantage.

The upshot: in 2026 I default to Node for anything that hits a database or uses an SDK, and reach for Edge only when latency-from-end-user actually matters (auth gates, A/B variants, streaming AI responses, personalization headers).

Which database drivers work on the Edge?

This is the single most asked question in our Discord, so it gets its own section. The Edge Runtime can't open raw TCP sockets, which rules out any driver that speaks the native Postgres or MySQL wire protocol. That includes pg, postgres, mysql2, and the default Prisma engine. Drivers that work over HTTP or WebSocket fetch, on the other hand, are fine.

Driver / ServiceEdge supportNotes
Neon Serverless (@neondatabase/serverless)YesHTTP and WebSocket modes, both edge-compatible
Planetscale (@planetscale/database)YesHTTP-only driver, built for Edge
Turso / libSQL (@libsql/client)YesUse the web entry point
Supabase JS clientYesUses fetch, works in Edge
Upstash Redis (@upstash/redis)YesREST API, optimized for Edge
Drizzle ORMYesPair with any HTTP driver above
Prisma + TCP driverNoUse Prisma Accelerate or the Neon adapter instead
Mongoose / native MongoDB driverNoUse Mongo Data API via fetch

If you're already using Drizzle, our Drizzle ORM integration guide walks through both the Edge-compatible HTTP setup and the Node TCP setup side by side.

When should you use the Edge Runtime?

Reach for the Edge Runtime when the answer to every one of these questions is "yes":

  • Does the request need to be fast from the end-user's geographic location?
  • Can the route's logic be expressed with Web Standard APIs?
  • Does the bundle stay under 4 MB after minification?
  • Is the CPU work per request light (under ~50 ms of synchronous compute)?

Concrete cases where Edge shines:

Authentication and authorization checks

Verifying a JWT, reading a session cookie, and redirecting unauthenticated users belongs at the edge. The latency saved by short-circuiting an unauthenticated request before it ever reaches your origin compounds across every protected page. Pair this with our Auth.js v5 sessions and route protection guide for the full edge-side defensive stack, plus our rate limiting guide for the abuse side.

A/B testing and personalization

Rewriting URLs based on a cookie, a country, or an experiment bucket is essentially what middleware was designed for. The decision needs to happen before rendering, so doing it at the edge avoids a round trip to the origin.

Streaming AI responses

// app/api/chat/route.ts
import { streamText } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'

export const runtime = 'edge'

export async function POST(req: Request) {
  const { messages } = await req.json()
  const result = streamText({
    model: anthropic('claude-sonnet-4-6'),
    messages,
  })
  return result.toUIMessageStreamResponse()
}

LLM token streams are bounded by network latency, not CPU, so running the proxy at the edge nearest the user shaves real perceived milliseconds off time-to-first-token.

When should you stick with the Node.js Runtime?

Honestly, Node is the right default for most routes. Use it whenever you need any of the following:

Database writes with a full ORM

Prisma's standard engine, Sequelize, and TypeORM all need TCP. Even with Drizzle, if you're using node-postgres for connection pooling, you need Node. Don't fight the runtime. Put the route in Node, put a cache in front of it.

File system access

Reading the public/ directory at runtime, writing to /tmp, parsing markdown files at request time, all require fs. If you're building a content site with on-demand MDX rendering, you need Node.

Native or large dependencies

Sharp (image processing), Puppeteer/Playwright (PDF generation, screenshots), node-canvas, native cryptography modules (argon2, bcrypt) all require Node. So does anything that calls require() dynamically.

Long-running tasks

If a request might take more than a few seconds (generating a report, processing an upload, calling a slow third-party API), set maxDuration on a Node route up to 300 seconds on Vercel Pro or 800 on Enterprise. Edge functions can't be extended past 30 seconds.

Streaming, AI, and edge-friendly patterns

The Edge Runtime's first-class Web Streams support makes it the natural home for AI workloads. The pattern looks like this:

// app/api/summarize/route.ts
export const runtime = 'edge'

export async function POST(req: Request) {
  const { url } = await req.json()
  const article = await fetch(url).then(r => r.text())

  const upstream = await fetch('https://api.anthropic.com/v1/messages', {
    method: 'POST',
    headers: {
      'x-api-key': process.env.ANTHROPIC_API_KEY!,
      'anthropic-version': '2023-06-01',
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      model: 'claude-sonnet-4-6',
      max_tokens: 1024,
      stream: true,
      messages: [{ role: 'user', content: `Summarize:\n${article}` }],
    }),
  })

  // Pass the SSE stream straight through to the browser.
  return new Response(upstream.body, {
    headers: { 'content-type': 'text/event-stream' },
  })
}

Notice there's no body buffering — the route is a thin proxy. That's the right shape for Edge: short, async I/O bound, streaming. For deeper patterns on progressive UI updates, see our guide to streaming and Suspense.

Migration pitfalls I've hit in production

A few footguns from moving routes between runtimes that have cost me real debugging hours (in one case, an entire Friday afternoon I'd rather forget):

Hidden Node imports in shared libraries

A shared lib/auth.ts that imports jsonwebtoken works fine in your Node routes. Move it to Edge and you'll discover jsonwebtoken reaches for node:buffer. Swap to jose, which is Edge-native, or split the module so Edge-only consumers import an Edge-only entry point. The jose GitHub repo has a clean Edge example in its README.

Process.env is fine, dotenv is not

process.env.MY_VAR works in Edge. Next.js inlines values at build time. But require('dotenv').config() won't, because it touches fs. Trust the framework's env handling instead of reaching for dotenv at runtime.

Cookies and headers are read-only in fetch handlers

The Headers object on a Request is immutable. To mutate cookies, return a Response with Set-Cookie. In middleware, use NextResponse.cookies.set(...).

Caching gotchas

Edge routes can call fetch with Next's cache options, but the 'use cache' directive from Next.js 16's Cache Components is currently Node-only. If you've adopted Cache Components, that section of your app is implicitly Node-bound.

Logging

Edge has no process.stdout. console.log works (Vercel captures it), but anything pulling in Winston, Pino with file transports, or OpenTelemetry's Node exporter will fail. Use the lighter @vercel/otel or write a small fetch-based logger.

Frequently Asked Questions

Is the Next.js Edge Runtime faster than Node.js?

It's faster on cold-start latency and on time-to-first-byte from distant regions, but not faster on raw CPU throughput. For a hot path that runs in the same region as the user, Fluid Compute Node functions are now competitive on cold start and faster on sustained CPU work.

Can I use Prisma with the Edge Runtime?

Only via Prisma Accelerate (HTTP) or the Neon adapter. The default Prisma engine uses TCP and is incompatible with Edge. For PostgreSQL on Edge, @neondatabase/serverless paired with Drizzle is the smoother path.

Does middleware always run on the Edge Runtime?

By default yes, but since Next.js 15.2 you can opt middleware (now proxy.ts in Next.js 16) into the Node.js Runtime by exporting runtime = 'nodejs'. The Node mode unlocks Node-only auth libraries at the cost of cold-start latency and losing global distribution.

What is the code size limit for an Edge function?

On Vercel, 4 MB of compiled, minified JavaScript on Pro and Enterprise plans, and 1 MB on Hobby. Self-hosted Edge Runtime has no hard cap but inherits whatever your platform sets.

Should I use Edge Runtime for everything?

No. Default to Node for database writes, image processing, PDF generation, and anything that uses native modules. Use Edge for auth gates, A/B routing, geolocation-driven personalization, and AI streaming responses where the user is global.

Editorial Team
About the Author Editorial Team

Our team of expert writers and editors.