Node.js errors
Status: 🟩 COMPLETE Last updated: 2026-06-21 Plain-English tagline: The Node-level errors you’ll see in dev or in CI — ENOENT, EADDRINUSE, version mismatches, the cryptic file-system and network errors. Paste the code; find the fix.
What this is
A reference for Node.js runtime errors. These show up:
- In your terminal during
npm run devornpm run build - In CI logs (GitHub Actions, Vercel build logs)
- In server-side code that throws Node-specific exceptions
For Vercel deployment runtime errors specifically, see Vercel runtime errors 🟩 🟦.
How Node error codes work
Most Node errors include a code like ENOENT, EADDRINUSE, ECONNREFUSED. The E prefix means “error”; the rest is the category. These come from underlying system calls.
try {
await fs.readFile("/missing/file.txt");
} catch (e) {
console.log(e.code); // "ENOENT"
console.log(e.message); // "ENOENT: no such file or directory, open '/missing/file.txt'"
}Catching by code is more reliable than parsing the message.
”ENOENT: no such file or directory”
What it means: A file/folder operation referred to a path that doesn’t exist.
Common causes:
- Typo in the path.
- Working directory isn’t what you think. Scripts run from where you launch them, not where they live.
- File was generated at build time but isn’t in production. Vercel only ships files that are imported or in
public/. - Path separator mix-up.
'./folder\\file.txt'works on Windows, fails on Linux.
Fix:
- Use absolute paths when possible:
import path from "node:path"; const filepath = path.join(process.cwd(), "data", "file.txt"); - Or import the file instead of fs-reading it:
import data from "./data.json"; // bundlers handle this - For optional files, check existence:
try { await fs.access(filepath); } catch { // doesn't exist; use default }
“EADDRINUSE: address already in use :::PORT”
What it means: Another process is bound to the port you’re trying to listen on.
Common causes:
- A previous
npm run devdidn’t shut down cleanly - Another app is using the port (Docker container, OS service)
- You opened multiple terminals each running
npm run dev
Fix:
# Windows PowerShell — find what's using port 3000
Get-NetTCPConnection -LocalPort 3000 | Select-Object OwningProcess
Stop-Process -Id <pid># macOS / Linux
lsof -i :3000
kill <pid>Or just use a different port: next dev -p 3001.
”ECONNREFUSED 127.0.0.1:PORT” or similar
What it means: You tried to connect to a service on a port, and nothing’s listening there.
Common causes:
- Local service isn’t running. Forgot to
supabase startbefore running tests. - Wrong port. Supabase local DB is 54322, not 5432.
- Service crashed. Check its log.
- Firewall blocking.
Fix: verify the target service is actually running and listening on the expected port.
”ECONNRESET” or “socket hang up”
What it means: The remote end abruptly closed the connection during a request.
Common causes:
- Server timed out and dropped the connection
- Network blip
- The server crashed mid-request
- TLS handshake failure
Fix:
- Add retries with exponential backoff for flaky external services.
- Increase request timeout if the server is just slow.
- Check the server’s logs for what it saw.
async function fetchWithRetry(url: string, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fetch(url);
} catch (e) {
if (i === attempts - 1) throw e;
await new Promise(r => setTimeout(r, 2 ** i * 1000));
}
}
}”EACCES: permission denied”
What it means: The user running the process doesn’t have permission for the file/folder operation.
Common causes:
- Trying to write to a system directory
- Running
npm install -gon Linux/macOS withoutsudo - Docker volumes with wrong ownership
Fix:
- For npm global installs: change the npm prefix to a user-owned folder, so you never need
sudo: Then addnpm config set prefix "$HOME/.npm-global"$HOME/.npm-global/binto your PATH. - For Docker: match the container user to the host user.
- For local files: check ownership with
ls -l(orGet-Aclon Windows).
”EMFILE: too many open files”
What it means: The process exceeded the OS limit on open file descriptors.
Common causes:
- A script reading thousands of files in parallel without closing them
- A leak — files opened but never closed (
fs.createReadStreamwithout.close()) - Watcher (e.g. Next.js dev) running out of slots
Fix:
- Limit parallelism — use
p-limitlibrary or batch operations. - Make sure streams are closed — use
try/finallyorawait using(TC39 explicit resource management). - Raise the OS limit (last resort):
ulimit -n 4096on macOS/Linux.
”Unhandled Promise Rejection”
What it means: A Promise rejected and no .catch() or try/catch handled it.
In Node 16+, unhandled rejections crash the process by default.
Fix:
// BAD
fetch("/api").then(r => r.json());
// GOOD
fetch("/api").then(r => r.json()).catch(handleError);
// GOOD
try {
const r = await fetch("/api");
const data = await r.json();
} catch (e) {
handleError(e);
}For global safety net (in long-running servers):
process.on("unhandledRejection", (reason, promise) => {
console.error("Unhandled Rejection at:", promise, "reason:", reason);
});This logs but doesn’t fix — find and fix the underlying missing .catch().
”ERR_REQUIRE_ESM” or “Cannot use import statement outside a module”
What it means: Mixing ES Modules and CommonJS in an incompatible way.
Common causes:
- A
.jsfile usesimportbut the package isn’t configured as ESM - An ESM-only package was
require()’d from CommonJS
Fix:
- For files using
import: add"type": "module"topackage.jsonOR rename to.mjs. - For Next.js projects: this is usually fine — Next.js handles both. The error means an external script is misconfigured.
- For an ESM-only dep used from CommonJS: convert your code to ESM, or use dynamic import:
const pkg = await import("esm-only-pkg").
”node: bad option” or “Unsupported flag”
What it means: A flag in your package.json scripts is wrong for the installed Node version.
Common causes:
--experimental-vm-modulesetc. only work in certain Node versions- Mix of Node 16-era and Node 20+-era flags
Fix:
- Pin Node version in
package.json:{ "engines": { "node": ">=20.0.0" } } - Use
.nvmrcto pin locally:20 - Vercel: specify Node version in
package.jsonenginesfield; Vercel reads it.
”ERR_INVALID_ARG_TYPE” / “TypeError [ERR_INVALID_ARG_TYPE]”
What it means: A Node API was called with the wrong type for an argument.
Common causes:
- Passed
undefinedwhere a string was expected - Passed an object where a path was expected
- Passed
nullto something that expected an array
Fix: read the message — it tells you which argument and what was expected. Trace back to where the bad value came from.
”Maximum call stack size exceeded”
What it means: Infinite recursion. A function calls itself (directly or via a chain) without a base case.
Fix: find the loop in the stack trace and add the base case.
Reference: Recursion đźź©
“OutOfMemory” or “JavaScript heap out of memory”
What it means: Node ran out of allocated memory. Default cap is ~2 GB on Windows, ~4 GB on Linux.
Common causes:
- Loading a huge dataset into memory at once
- Memory leak (objects retained when they should be GC’d)
- Big Next.js build (rare but possible for very large projects)
Fix:
- Increase the heap size:
NODE_OPTIONS="--max-old-space-size=8192" npm run build$env:NODE_OPTIONS="--max-old-space-size=8192" npm run build - Stream instead of load — for big files, use Node streams.
- Audit for leaks —
node --inspectand Chrome DevTools’ Memory tab.
”ERR_MODULE_NOT_FOUND” (in ESM mode)
What it means: ESM mode is strict about file extensions. import { x } from "./foo" fails — must be "./foo.js".
Fix:
- Add the extension in imports:
import x from "./foo.js". - Or use
"moduleResolution": "bundler"intsconfig.jsonso TypeScript handles extension resolution. - For Node 22+: use
--experimental-strip-typesto run.tsdirectly.
See also
- Common errors — index 🟩
- Build errors đźź©
- Vercel runtime errors 🟩 🟦
- Browser errors đźź©
- Node.js runtime 🟩 — the textbook
- npm & package managers đźź©
- npm cheat sheet đźź©
- Files and folders 🟩 — for path issues
- command line 🟩 — for process management