What this usually means
When running code in the Next.js Edge Runtime (middleware, edge API routes), standard Node.js APIs like fs, path, crypto, or child_process are not available. This is because Edge Runtime uses a restricted environment similar to Cloudflare Workers—no file I/O, native modules, or most server-side Node packages. Errors often stem from dependencies (direct or transitive) that import these unsupported modules, even if not used at runtime. Sometimes, server-only code is accidentally bundled into edge code due to misconfiguration or build tool bugs.
The first ten minutes — establish facts before touching code.
- 1Search build output for 'Error: Module not supported in edge runtime' to find the offending file.
- 2Grep codebase for direct imports of 'fs', 'path', or other Node modules: grep "from 'fs'" -r .
- 3Check which dependencies are used in edge routes/middleware by running npx modtree <entry file>
- 4Review next.config.js for incorrect runtime: 'nodejs' vs 'edge'
- 5Temporarily comment out likely problematic imports to isolate the issue
- 6Deploy with Vercel CLI and inspect vercel build output for module resolution details
The specific files, logs, configs, and dashboards that usually own this bug.
- searchMiddleware source files: middleware.ts, api/edge/*.ts
- searchnext.config.js — look for runtime and experimental edge flags
- search.next/server/edge-manifest.json for route-runtime mapping
- searchyarn.lock or package-lock.json for dependency trees
- searchVercel/Vercel CLI build logs (especially under 'edge functions')
- searchnode_modules of both direct and transitive dependencies for Node API usage
Practical causes, not theory. These are the things you will actually find.
- warningA helper imported in middleware pulls in Node.js modules (e.g., a shared util using 'fs')
- warningThird-party library is not ESM/browser compatible (e.g., uses 'crypto')
- warningnext.config.js misconfigures middleware or API routes to use 'edge' runtime accidentally
- warningTransitive dependency deep in the tree requires a Node API unexpectedly
- warningCode split incorrectly, bundling server logic into edge bundles
- warningMixing getServerSideProps logic or server APIs into edge handlers
Concrete fix directions. Pick the one that matches your root cause.
- buildRefactor helpers shared between server and edge to be edge-safe (no Node APIs or switch on runtime)
- buildReplace Node APIs (e.g., 'crypto') with browser equivalents or Web APIs (e.g., Web Crypto API)
- buildUse conditional imports: dynamically require Node modules only on server runtime
- buildPin or swap third-party packages with Edge-compatible versions (e.g., use jose instead of jsonwebtoken)
- buildExplicitly set runtime: 'nodejs' in next.config.js for pages not meant for the edge
- buildAudit and separate utility modules so edge code never imports server-only logic
A fix you cannot prove is a guess. Close the loop.
- verifiedRun edge functions locally using vercel dev and confirm no errors in terminal
- verifiedDeploy to preview and test affected routes—look for 200 status, not 500 or 504
- verifiedCheck Vercel dashboard Edge Functions tab for successful executions
- verifiedInspect bundle outputs: run npx modtree to confirm no Node modules in edge bundles
- verifiedWrite integration tests hitting edge endpoints to confirm they complete without error
- verifiedVerify that conditional code is dead-stripped from edge bundles by inspecting .next output
Things that make this bug worse or harder to find.
- warningDon't import server-only modules into shared utils consumed by both edge and server code
- warningDon't assume a package is edge-compatible just because it’s ESM or 'universal'
- warningNever ignore build warnings about unsupported modules—they often signal deeper issues
- warningAvoid broad imports (e.g., import * from 'utils') when only some exports are edge-safe
- warningDon't blindly set all API routes to edge runtime; only use when necessary
- warningDon’t trust that 'it works locally' means it will work at the edge—local dev often falls back to Node.js
Edge Middleware Breaks After Utility Module Refactor
Timeline
- 09:20Deployed feature branch adding analytics tracking to middleware.ts
- 09:22Vercel build fails: 'Module not supported in edge runtime: fs'
- 09:25Local dev passes; initial suspicion falls on new npm package
- 09:30Grep shows 'fs' only used in server utils, but imported in analyticsHelper.ts
- 09:32Realize analyticsHelper is shared between middleware and pages/api
- 09:41Split analyticsHelper: created analyticsHelper.edge.ts for edge-safe logic
- 09:48Re-deploy succeeds, edge middleware now works in preview and prod
I shipped an update to our Next.js middleware to add analytics for edge requests, pulling in a helper module already used on the server. Everything worked locally—no errors, all tests passing. But Vercel's build logs showed 'Module not supported in edge runtime: fs', and the middleware endpoint returned a 500 in preview.
I first assumed the new analytics NPM package was the culprit. Tracing through the imports, I found our analyticsHelper pulled in another server-only helper that imported 'fs' for debug logging, even though edge middleware never directly called that function. Because the module was imported top-level, it broke the whole edge bundle.
The solution was to refactor analyticsHelper, splitting it into analyticsHelper.edge.ts (with only edge-compatible code) and analyticsHelper.server.ts for server logic. After updating middleware.ts to import only the edge-safe helper, the build and deployment went through cleanly.
Root cause
Shared utility module imported by edge middleware transitively required 'fs', a Node-only API.
The fix
Refactored and split utility modules so edge code only imports edge-safe logic.
The lesson
Even unused imports in shared modules can break edge runtime—always isolate edge and server code paths.
Edge Runtime in Next.js is fundamentally different from classic Node.js: it runs in a sandbox with no access to filesystem, environment variables (except via public API), or native Node modules. Anything that assumes Node’s global APIs will fail at build or runtime.
Most issues trace to either library authors assuming Node, or engineers unaware that importing a module—even if not executing code—causes bundlers to include unsupported code in the edge bundle.
Sometimes, upgrading a dependency introduces a breaking change that’s not edge-compatible, even if prior versions worked. Always check package release notes for edge/browser compatibility before bumping versions if you’re using them in edge handlers.
Pinning to known-good versions or using resolutions in package.json can temporarily unblock you while seeking a permanent solution.
Pattern: Use if (process.env.NEXT_RUNTIME === 'nodejs') to conditionally require or import Node-only modules. However, beware that static analysis during the build can still include problematic modules unless imports are truly dynamic.
For shared utils, export two entrypoints: one for edge (no Node imports), one for server. Document this clearly to avoid future regressions.
Frequently asked questions
Why does my code work locally but fail in Edge Runtime?
Local dev often runs on Node.js, which tolerates Node APIs. Edge Runtime replicates a browser-like environment and fails on Node-specific imports.
How can I tell if a package is edge-runtime compatible?
Check its documentation for edge or browser support, inspect its source for Node API usage, and verify with a minimal edge handler import before using in middleware.
What Node APIs are banned in Edge Runtime?
All file system, process, child_process, net, tls, and most crypto APIs are unsupported. Only pure JavaScript and Web APIs are safe.
Is it safe to share utils between edge and server code?
Only if the shared utils never use or import any Node.js APIs. Otherwise, create explicitly separated files for edge-safe logic.
Can I use environment variables in Edge Runtime?
You can, but only those exposed and configured via Next.js public runtime or Vercel’s Edge config—not process.env directly.