ERR_REQUIRE_ESM — require() of an ES Module
Quick answer: a package you require() is now ESM-only and cannot be loaded with require(). Three fixes: pin the last CommonJS version (e.g. node-fetch@2, chalk@4), convert your project to ESM ("type": "module" + import), or load it with a dynamic import() from your CommonJS code.
What the error looks like
Node names the ESM package and even suggests the fix:
Error [ERR_REQUIRE_ESM]: require() of ES Module
/app/node_modules/node-fetch/src/index.js from /app/server.js not supported.
Instead change the require of index.js to a dynamic import() which is
available in all CommonJS modules.
The package in the path (here node-fetch) went ESM-only, and your file used require() to load it.
Why it happens
The package went ESM-only
node-fetch 3, chalk 5, nanoid 4, got 12 and others dropped CommonJS in a major release.
Your project is CommonJS
require() is synchronous and cannot load an ES module, which is asynchronous.
A transitive dep pulled ESM
An update bumped a dependency to an ESM-only version somewhere in the tree.
Mixed module systems
CommonJS and ESM in one project without the right package.json type or file extensions.
Diagnose it in three steps
Identify the ESM package
# the error path names it - e.g. node-fetch. Check its package.json:
cat node_modules/node-fetch/package.json | grep -E '"type"|"exports"'
# "type": "module" (and no CommonJS export) = ESM-only.Check your project's module system
grep '"type"' package.json
# missing or "commonjs" = you are CommonJS and cannot require() ESM.Pick a fix: pin, convert, or dynamic import
# fastest: pin the last CommonJS major
npm install node-fetch@2 chalk@4Pin it, convert to ESM, or dynamic import()
Three valid paths. Pinning the CommonJS version unblocks you immediately; converting to ESM is the durable, forward path; a dynamic import() lets a CommonJS file load the ESM package without a full migration.
// A) Pin the last CommonJS version (fastest)
npm install node-fetch@2 chalk@4
// B) Convert your project to ESM - package.json:
{ "type": "module" }
// then use import, not require:
import fetch from 'node-fetch';
// C) Dynamic import() from CommonJS (no migration):
async function getData() {
const { default: fetch } = await import('node-fetch');
return fetch('https://example.com');
}
For a new project, start with ESM. For an established CommonJS app on a deadline, pin the version and plan the ESM migration separately so it does not block the deploy.
Pinned dependencies, caught before prod
An ESM-only bump that breaks require() is a dependency-drift problem - an unpinned update changed behavior under you. On your own servers, Infraveil runs deploys from a pinned, reproducible build and catches a failed start before it serves traffic, so a surprise ESM bump shows up in the pipeline, not in production - with recovery approval-gated and recorded.
Frequently asked questions
What does ERR_REQUIRE_ESM mean?
You used require() on a package that ships only as an ES module. require() cannot load ESM, so Node throws and suggests a dynamic import() instead.
Why did this start after a package update?
The package released a major that dropped CommonJS and went ESM-only - node-fetch 3, chalk 5, nanoid 4 and others did this. Pin the previous major or migrate to ESM.
Should I convert my project to ESM?
It is the forward path and the better default for new projects, but it is a migration for an existing CommonJS app. For a quick unblock, pin the CommonJS version of the package.
Can I require() an ESM module at all?
Not directly. From CommonJS use a dynamic import(), which returns a promise: const { default: x } = await import('pkg'). To use plain import syntax, the file itself must be ESM.
Client-side, no signup — they run in your browser.