Skip to content
Latchkey

Node ERR_MODULE_NOT_FOUND - Fix ESM Import Resolution in CI

Under native ESM, Node does not guess file extensions the way CommonJS did. An import without an explicit extension (or to a non-existent path) fails with ERR_MODULE_NOT_FOUND.

What this error means

A type: module package (or .mjs) crashes resolving a relative import with ERR_MODULE_NOT_FOUND. It frequently appears after compiling TypeScript to ESM, where emitted imports lack the .js extension.

Node output
node:internal/errors ... [ERR_MODULE_NOT_FOUND]:
Cannot find module '/app/dist/utils' imported from /app/dist/index.js
Did you mean to import ./utils.js?

Common causes

Missing file extension in ESM imports

Native ESM requires the full specifier, including the extension. import x from "./utils" fails; ./utils.js is required.

TypeScript emitted extensionless imports

TS source written ./utils and the compiler did not rewrite it to ./utils.js, so the emitted ESM cannot be resolved by Node.

Wrong path or missing index resolution

ESM does not auto-resolve directory index.js the way CJS does unless explicitly configured; a bare directory import fails.

How to fix it

Use explicit extensions in ESM

Add the file extension to relative imports.

TypeScript / JS
// before
import { x } from './utils';
// after
import { x } from './utils.js';

Align module settings

  1. Set tsconfig module/moduleResolution to a Node ESM mode (e.g. NodeNext).
  2. Reference directory entry points explicitly (./dir/index.js).
  3. Verify dist output paths exist and match the import specifiers.

How to prevent it

  • Use explicit .js extensions in ESM/TS-to-ESM imports.
  • Adopt NodeNext module resolution in tsconfig.
  • Test the built ESM output in CI, not just the TS source.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →