Skip to content
Latchkey

esbuild "Dynamic require of 'fs' is not supported" in CI

esbuild bundled CommonJS code into an ESM output where require does not exist natively. A runtime require(...) of a Node builtin then throws "Dynamic require is not supported".

What this error means

The bundle builds but crashes at runtime in CI with "Dynamic require of 'fs' is not supported". A dependency calls require() dynamically and the ESM output has no require to satisfy it.

node
Error: Dynamic require of "fs" is not supported
    at file:///work/repo/dist/index.js:1:1234

Diagnose it: what is different about the runner?

A build that passes locally and fails on a runner differs in a small number of predictable ways. Check those before changing build configuration, because the build config is usually not the thing that changed.

.github/workflows/ci.yml
- run: |
    node --version && npm --version
    echo "NODE_ENV=$NODE_ENV  CI=$CI"
    nproc && free -h && df -h /
    ls -la node_modules/.bin | head

Common causes

CJS bundled to ESM without a require shim

Output format is esm but the bundled dependency still calls require() at runtime, which the ESM output cannot provide.

Node builtins were bundled instead of externalized

Builtins like fs were pulled into the bundle, so esbuild emitted a dynamic require it cannot resolve.

How to fix it

Inject a require shim for ESM output

Recreate require with createRequire and inject it via a banner so bundled CJS can call require at runtime.

build script
await esbuild.build({
  format: 'esm',
  platform: 'node',
  banner: { js: "import{createRequire}from'module';const require=createRequire(import.meta.url);" },
});

Externalize Node builtins and dependencies

Keep builtins (and node_modules) out of the bundle so the runtime resolves them normally.

build script
esbuild.build({ platform: 'node', external: ['fs', 'path'], packages: 'external' });

The three that account for most of them

  • Case sensitivity. Linux runners are case sensitive, macOS is not. An import with the wrong case resolves locally and fails in CI.
  • Out of memory. Exit code 137 is a SIGKILL from the kernel, not a build error. Raise --max-old-space-size or use a larger runner.
  • devDependencies pruned. NODE_ENV=production makes npm ci skip devDependencies, so the build tool itself goes missing. Set it after install, not before.

How to prevent it

  • Set platform: "node" when bundling server code.
  • Externalize Node builtins or inject a createRequire banner for ESM output.
  • Test the built artifact in CI, not just the source.

Frequently asked questions

What causes esbuild "Dynamic require of 'fs' is not supported" in CI?
There are 2 common causes: cjs bundled to esm without a require shim and node builtins were bundled instead of externalized. Output format is esm but the bundled dependency still calls require() at runtime, which the ESM output cannot provide.
How do I fix esbuild "Dynamic require of 'fs' is not supported" in CI?
There are 2 fixes depending on which cause you have: inject a require shim for esm output and externalize node builtins and dependencies. Work through them in order, since the first is the most common.
What does esbuild "Dynamic require of 'fs' is not supported" in CI actually mean?
The bundle builds but crashes at runtime in CI with "Dynamic require of 'fs' is not supported".
How do I stop esbuild "Dynamic require of 'fs' is not supported" in CI happening again?
Set platform: "node" when bundling server code. The prevention section lists 3 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card