skip to content

Edge Runtime Constraints

Middleware runs in a Web-standard runtime, not Node, so your ORM, your fs call, and half your npm dependencies simply won't load. Interviewers ask what you can't do at the edge and what the escape hatch is.

part ofNext.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In a Next.js App Router project, importing a Node-only library such as a TCP database driver into `middleware.ts` fails the build. Which runtime does middleware execute in, and what does that runtime not provide?

level: middleimportance: must knowfreq 72%

answer

  1. not Node — a different runtime
  2. Web-standard globals only
  3. no sockets, no filesystem, no native addons
  4. the bad import is usually transitive
  5. route handler runs Node instead

basics

~20 s

Next.js middleware runs in the Edge Runtime, a V8 isolate exposing only Web-standard APIs such as fetch and Web Crypto. Node built-ins like fs, net and tls, and packages with native addons, are absent, so a TCP database driver cannot bundle.

solid answer

~50 s

Middleware does not run in Node. It is compiled into its own bundle that executes in Next's Edge Runtime — a V8 isolate whose globals are Web-platform APIs: `fetch`, `Request`, `Response`, `Headers`, `URL`, `TextEncoder`, Web Crypto through `crypto.subtle`, and the stream types. `process.env` is available. What is missing is everything Node layers on top: `fs`, `net`, `dns`, `tls`, `child_process`, and any package shipping a native `.node` addon. A TCP driver needs raw sockets, so it cannot even be bundled, and the build fails with a message naming the unsupported Node module. Dynamic code evaluation — `eval`, `new Function` — is rejected there too. The offending import is usually transitive: a session helper pulls in an ORM that pulls in a socket library. The fix is placement — keep middleware to decisions you can make from the request plus `fetch`, and do Node-dependent work in a route handler or Server Component running the Node.js runtime.

go deeper

for a junior

Be able to say plainly that middleware does not run in Node and that Node built-ins such as fs and net are unavailable there, and name fetch as the way middleware talks to other services.

for a middle

Explain the mechanism: middleware is compiled into its own bundle for the Edge Runtime, whose globals are Web-platform APIs, so an unsupported import fails at build time — and note that the import is usually transitive through a shared helper.

for a senior

Show that you treat this as a placement decision. Describe how you split shared modules into edge-safe and Node halves so the same session helper does not drag a database driver into the middleware bundle.

for a principal

Own the boundary as an architectural rule: which tier is allowed which capabilities, how you keep that rule enforceable as the codebase grows, and what you give up in latency and portability if you opt middleware into the Node.js runtime instead.

## What the Edge Runtime is Next compiles `middleware.ts` (at the project root, or inside `src/`) into a bundle separate from the rest of your application, and that bundle executes in the **Edge Runtime**. The Edge Runtime is not a stripped-down Node; it is a different execution environment built on V8 isolates, whose global surface is deliberately limited to Web-platform APIs. That includes `fetch`, `Request`, `Response`, `Headers`, `URL`, `URLSearchParams`, `TextEncoder`/`TextDecoder`, `atob`/`btoa`, `structuredClone`, `AbortController`, `ReadableStream`/`WritableStream`/`TransformStream`, and the Web Crypto object exposed as the global `crypto` (`crypto.subtle`, `crypto.randomUUID`, `crypto.getRandomValues`). `process.env` is readable, with values inlined at build time. What is absent is everything Node adds on top of the language: `fs`, `path`-adjacent filesystem work, `net`, `dns`, `tls`, `child_process`, `worker_threads`, and any dependency that ships a compiled native addon. There is no filesystem to read from and no socket to open, so libraries built on those primitives cannot be made to work by configuration — the capability is not there. ## Why the failure shows up as a build error Because the middleware bundle is produced ahead of time, an unsupported import is caught during `next build` rather than at request time. Next reports a message of the form "the edge runtime does not support Node.js '<module>' module", naming the built-in that was reached. A second, related class of failure is dynamic code evaluation: `eval`, `new Function`, and compiling WebAssembly from a runtime-produced buffer are disallowed in the Edge Runtime, so libraries that build functions from strings (some template engines, some validators) fail even though they touch no Node built-in at all. ## The transitive-import trap Almost nobody writes `import fs from 'node:fs'` in middleware on purpose. The realistic failure is three hops deep: ```ts // middleware.ts import { getSession } from '@/lib/session' // → imports the ORM client // → imports a Postgres driver // → imports 'net' ``` The lesson is that middleware's dependency graph is a constraint on your shared library code. A `lib/session` module that is safe in a Server Component may be unusable in middleware, which is a good reason to split "read and verify the cookie" (Web-standard, edge-safe) away from "load the user row" (Node, database). ## What middleware can legitimately do Plenty, as long as it stays inside the Web platform: read incoming cookies and headers off `NextRequest`, inspect the URL, call an HTTP service with `fetch`, verify a signed token with Web Crypto, and produce a response with `NextResponse`. Token libraries written against Web Crypto (for example `jose`) work; ones written against Node's `crypto` module (for example `jsonwebtoken`) do not. ## The escape hatch Anything genuinely Node-dependent belongs in a surface that runs in the Node.js runtime — a route handler under `app/api/**/route.ts`, a Server Component, or a Server Action. Those default to the Node.js runtime, and you can state it explicitly with the segment config export: ```ts // app/api/report/route.ts export const runtime = 'nodejs' ``` The same export accepts `'edge'` if you want a route handler to run under the Edge Runtime and inherit exactly the constraints described above. ## Version caveat Through the Next 15 line, `middleware.ts` runs in the Edge Runtime by default; Next 15.2 introduced an opt-in Node.js runtime for middleware via `export const config = { runtime: 'nodejs' }`, which later stabilised. Because this default has moved, the durable way to answer is by mechanism — "middleware is bundled for whichever runtime it is configured for, and the Edge Runtime supplies only Web-standard APIs" — and then to check what your installed version and hosting platform actually do. Opting middleware into Node buys you the Node API surface at the cost of the edge execution profile, and not every deployment target supports it. ## How to recognise it in an interview The tell that someone has actually shipped this is that they describe the failure as a *placement* problem, not a bundler problem: the question is not "how do I polyfill `fs` here" but "which tier of the app should be doing this work at all".

  • Your middleware imports nothing from Node directly, yet the build still complains about an unsupported Node module. How do you find the culprit?
    Follow the import chain the error prints, or bisect by commenting out imports in `middleware.ts` one at a time. The cause is nearly always a shared helper that transitively reaches a Node built-in — a session or database module. The durable fix is splitting that helper: an edge-safe half that only reads and verifies the request, and a Node half that talks to the database.
  • Can you use environment variables inside Next.js middleware, and does anything about them behave differently there?
    Yes — `process.env` is readable in middleware. The difference is that values are inlined at build time for the edge bundle rather than read from a live process environment, so a variable changed after the build is not picked up without rebuilding or redeploying, depending on the platform. Treat middleware config as build-time config, not runtime config.
  • Some libraries fail in middleware even though they never import a Node built-in. Why?
    Because the Edge Runtime also forbids dynamic code evaluation. `eval`, `new Function`, and compiling WebAssembly from a runtime buffer are rejected, so any library that constructs functions from strings — certain template engines, schema compilers and expression evaluators — fails there regardless of its module imports.

saying these in an interview costs you the question

  • Says middleware is just Node with fewer packages installed
  • Thinks a bundler alias or polyfill can restore fs or net
  • Claims you can query the database directly from middleware
  • Assumes any npm package works if it is pure JavaScript
  • Confuses the Edge Runtime with running React on the client

context

open as a page

A Next.js team wants per-request logic that needs a Node-only SDK and a database lookup, and the logic feels like it belongs in `middleware.ts`. How would you restructure it so it can actually run?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Split the work by runtime. Keep in middleware only what the Edge Runtime can do from the request itself, and move the Node SDK and database lookup into a route handler, Server Component or Server Action running the Node.js runtime, which is their default.

open as a page

You need a SHA-256 hash of a cookie value inside `middleware.ts` in a Next.js app. Why does `import { createHash } from 'node:crypto'` not work there, and what do you use instead?

level: juniorimportance: should knowfreq 46%

basics

~20 s

Node's crypto module is not part of the Edge Runtime that Next.js middleware runs in. Use the Web Crypto global instead: encode the string with TextEncoder and await crypto.subtle.digest('SHA-256', bytes), then format the resulting ArrayBuffer yourself.

open as a page

Next.js middleware executes on every request it is applied to, before the response is produced. What size and CPU limits does that impose on it, and how do you stay inside them?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Middleware is one bundle on the hot path, so hosts cap its bundled size and give each invocation a short CPU budget. Keep dependencies tiny and edge-native, avoid embedding data files, and never do heavy computation or slow awaits there.

open as a page