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 dev or npm 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:

  1. Typo in the path.
  2. Working directory isn’t what you think. Scripts run from where you launch them, not where they live.
  3. File was generated at build time but isn’t in production. Vercel only ships files that are imported or in public/.
  4. 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 dev didn’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:

  1. Local service isn’t running. Forgot to supabase start before running tests.
  2. Wrong port. Supabase local DB is 54322, not 5432.
  3. Service crashed. Check its log.
  4. 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 -g on Linux/macOS without sudo
  • Docker volumes with wrong ownership

Fix:

  • For npm global installs: change the npm prefix to a user-owned folder, so you never need sudo:
    npm config set prefix "$HOME/.npm-global"
    Then add $HOME/.npm-global/bin to your PATH.
  • For Docker: match the container user to the host user.
  • For local files: check ownership with ls -l (or Get-Acl on 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.createReadStream without .close())
  • Watcher (e.g. Next.js dev) running out of slots

Fix:

  • Limit parallelism — use p-limit library or batch operations.
  • Make sure streams are closed — use try/finally or await using (TC39 explicit resource management).
  • Raise the OS limit (last resort): ulimit -n 4096 on 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 .js file uses import but the package isn’t configured as ESM
  • An ESM-only package was require()’d from CommonJS

Fix:

  • For files using import: add "type": "module" to package.json OR 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-modules etc. 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 .nvmrc to pin locally:
    20
    
  • Vercel: specify Node version in package.json engines field; 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 undefined where a string was expected
  • Passed an object where a path was expected
  • Passed null to 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 --inspect and 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" in tsconfig.json so TypeScript handles extension resolution.
  • For Node 22+: use --experimental-strip-types to run .ts directly.

See also


Sources