DEV Community

Multigrid
Multigrid

Posted on • Originally published at multigrid.ai

Why an Edge Runtime Can't Use a Node SDK for a Model Provider

The build fails with a module it cannot resolve, or the dev server prints that a Node.js API is not supported in the Edge Runtime, and the natural conclusion is that the provider’s SDK does not work on the edge. It almost certainly does. The error names a file, and that file is usually three levels down your dependency tree.

The errors

Four distinct messages, and they mean slightly different things:

  • Module not found: Can’t resolve 'fs' — the bundler could not find a Node built-in because the edge target does not provide one. Build-time, and the same shape appears for net, child_process, dns and tls.
  • The edge runtime does not support Node.js 'crypto' module. — a Node module that exists in name but is not implemented in this runtime.
  • A Node.js module is loaded ('url' at line 3) which is not supported in the Edge Runtime. — the same class of problem, reported with the location.
  • A Node.js API is used (process.cwd) which is not supported in the Edge Runtime. — not a module import but a global, which is why it can survive bundling and surface late.

Next.js documents these under a dedicated error page for Node modules in the Edge Runtime, whose remedy is stated bluntly: the module must be avoided, either by not importing it or by replacing it with a polyfill — for instance the Web Crypto API in place of Node’s crypto.

Why an isolate cannot do this

The mechanism is worth holding onto because it predicts which APIs will and will not exist without looking anything up. Vercel describes its Edge runtime as built on the V8 engine, running in isolated execution environments that do not require a container or a virtual machine. Netlify’s Edge Functions are Deno in an isolate; Cloudflare Workers are the same idea.

No container means no filesystem to open, no process table to fork into, and no raw socket to bind. Those are not features somebody forgot; they are the reason an isolate starts in single-digit milliseconds and costs a fraction of a container. So fs, child_process and net are not missing — they are precisely the capabilities that were traded away for the start-up time. A polyfill cannot restore them, because there is nothing underneath to polyfill onto.

Two further restrictions follow from the same design. Vercel documents that calling require directly is not allowed — modules must be ES modules — and that dynamic code evaluation is disabled: eval, new Function(evalString), WebAssembly.compile and WebAssembly.instantiate from a buffer. That last one is why a WASM-backed tokeniser or crypto library can fail even though it uses no Node APIs at all. Vercel notes that WebAssembly is supported when the source is provided via an import statement rather than compiled at runtime from bytes.

What is available

More than the error messages suggest. Vercel documents a set of Node modules importable with or without the node: prefix: async_hooks (the WinterCG subset, for AsyncLocalStorage), events, buffer, assert and util, with Buffer also exposed globally to maximise compatibility with existing Node modules.

On top of that sit the web APIs that matter for a model call: fetch, Request, Response, Headers, AbortController, the full Streams API, TextEncoder and TextDecoder, and Web Crypto with SubtleCrypto. process.env works. Netlify’s Deno-based edge runtime exposes an overlapping set and additionally allows Node built-ins with the node: prefix, along with npm packages — with the caveat Netlify states, that npm support is in beta and packages relying on native binaries or dynamic runtime imports may not work.

Which is enough to call any model provider. HTTP plus JSON plus a stream reader is the entire requirement.

Finding the actual culprit

The misdiagnosis this page exists to correct: modern provider SDKs are fetch-based and explicitly support these runtimes. OpenAI’s Node SDK lists Cloudflare Workers and the Vercel Edge Runtime among its officially supported runtimes, alongside Node, Deno and Bun. If your build is failing on fs, the provider client is rarely the reason.

The usual real causes, in rough order of frequency:

  • An authentication or session library. Session libraries reach for Node crypto for signing, and this is by some distance the most reported instance — a middleware file that imports an auth helper which imports a JWT library which imports crypto.
  • A database driver. Anything speaking a wire protocol over TCP needs net. HTTP-based drivers exist for most databases precisely because of this constraint.
  • An observability or logging SDK. These read the process environment, the filesystem and sometimes hostname information, which is where process.cwd and friends come from.
  • A utility package with one Node-only branch. A library that uses fs in a code path you never call still gets traced by the bundler, because static analysis cannot know the branch is dead.

Locate it from the message rather than by guessing. Next.js documents that running locally with next dev shows, in the console and in the browser, which file is importing and using the unsupported module — and the at line N in the message is a real location. Follow the import chain from that file upward to the top-level package, then decide whether you need it in this function at all.

The four fixes

  1. Move the function off the edge runtime. This is now Vercel’s own recommendation: its Edge Runtime reference opens by advising migration from edge to Node.js for improved performance and reliability, noting that both run on fluid compute with Active CPU pricing. If the function was on the edge for latency and it is spending 800 ms awaiting a model anyway, the cold-start advantage was never the binding constraint.
  2. Replace the dependency with a web-standard equivalent. Node crypto to crypto.subtle; a TCP database driver to its HTTP variant; a Node-only JWT library to one built on Web Crypto. This is the right fix when the function genuinely benefits from running at the edge.
  3. Drop the SDK and call the HTTP API. A completions request is a fetch with three headers and a JSON body. For a single endpoint the SDK is often carrying more compatibility risk than convenience, and edge code size limits — Vercel documents 1 MB on Hobby, 2 MB on Pro and 4 MB on Enterprise after gzip — reward the smaller dependency independently.
  4. Split the work. Keep the cheap, latency-sensitive part at the edge — auth check, geo routing, cache lookup — and forward to a Node function for anything needing Node APIs. This is usually better than forcing one runtime to do both jobs.

One structural change worth knowing about: Vercel documents that from Next.js 16.3, setting runtime = 'edge' is no longer supported and routes and pages run on Node.js. That removes this class of error from most Next.js applications entirely — but the underlying constraint is unchanged for Netlify Edge Functions, Cloudflare Workers and Next.js proxy code, which are all still isolates. Vercel, “Edge Runtime”, read 11 August 2026.

Fix three — drop the SDK, call the HTTP API — is the one that scales badly on its own, because each provider then means a hand-written request shape, a hand-written stream parser and a hand-written error mapping in code you cannot easily test in an isolate. A gateway gives you one HTTP surface to write against instead: Multigrid speaks a single request and stream format across providers, which is a plain fetch call and therefore runs unmodified in any of these runtimes.

Related

Top comments (0)