Node.js · beginner
Node.js ERR_MODULE_NOT_FOUND: trace the missing module
This guide explains how to diagnose Node.js ERR_MODULE_NOT_FOUND, the error thrown when the runtime cannot resolve an imported package or file. It walks from the error message and stack trace through module resolution, package export maps, and filesystem checks, ending with safe fixes and proof-of-fix steps.
The symptoms
- •Process exits with ERR_MODULE_NOT_FOUND at startup before any application code runs.
- •Error message contains the literal phrase "Cannot find module" followed by the resolved specifier.
- •Stack trace frames point to an import statement at a specific file and line in your source.
- •Behavior can differ between local development and a packaged build, container image, or CI workspace.
- •Both static import declarations and dynamic import() calls can raise the same error code.
Likely causes
- •The target package or file is missing from node_modules or the installed filesystem.
- •The imported specifier is misspelled, uses the wrong case, or omits a required file extension.
- •The package's package.json "exports" field does not expose the requested subpath.
- •A build step emitted output at a different path than the import statement expects.
- •package.json "type" does not match the actual file extensions and loader behavior on disk.
- •A peer or transitive dependency was removed or relocated by a lockfile or hoisting change.
First ten minutes
- 01Re-read the full error message and note the exact specifier it failed to resolve.
- 02Open the importing file at the reported line and confirm the import text character by character.
- 03From the project root, list the directory or node_modules path the error references.
- 04Run `node --version` and inspect package.json for the "type" field to confirm the module system.
- 05If a subpath is requested, open the target package's package.json and inspect the "exports" map.
- 06Compare the error against the last change you made to dependencies, imports, or build output.
Evidence to collect
- •Full error text including the resolved specifier and every stack frame.
- •The importing file's import statement and the surrounding code context.
- •Filesystem listing of the directory the error references, including hidden entries.
- •npm ls output for the package in question and the overall dependency tree.
- •The relevant package's package.json, especially the "exports", "main", and "module" fields.
- •node --version output and the project's package.json "type" and "engines" fields.
Where to look
- •Stack trace frame pointing to the import line in your source file.
- •node_modules at the project root and any nested node_modules directories.
- •The imported package's package.json, focused on the "exports" field.
- •Build output directories if a transpiler, bundler, or copy step is in use.
- •Container image layers or CI workspace if running outside local development.
- •process.cwd() at runtime, since relative imports resolve from the entry file's location.
Diagnostic steps
- 01Reproduce the failure with the smallest possible script that issues the failing import.
- 02From a module file, use import.meta.resolve to print exactly what Node resolved the specifier to.
- 03Run Node with NODE_DEBUG=module to trace each resolution decision before the failure.
- 04Compare the resolved path against an `ls` of the target directory to confirm presence and exact case.
- 05If importing a subpath, cross-check the package's "exports" map against the requested subpath.
- 06Switch between a direct `node <entry>.mjs` invocation and the project's normal start command.
- 07Use `node --check` on the importing file to confirm it parses before runtime resolution runs.
Common mistakes
- •Dropping the .js extension on a relative ESM import, which Node requires by default.
- •Assuming case-insensitive filesystem matching on Linux or macOS containers and CI runners.
- •Editing files inside node_modules instead of declaring the change through the package manager.
- •Importing a deep internal path that the target package does not expose via its "exports" map.
- •Treating a CommonJS resolution path as if it also worked for ESM, or vice versa.
- •Trusting a hoisted dependency that was replaced by a nested copy during install.
Safe fixes
- •Install the missing package using the project's package manager, then restart Node.
- •Add the explicit file extension (e.g., .js) to the relative ESM import statement.
- •Adjust the import to a subpath that the package's package.json "exports" field declares as public.
- •Rename the file or directory on disk so the import path matches exactly, including case.
- •Reinstall node_modules from the lockfile if a previous install left a partial or hoisted tree.
- •Align package.json "type" with the actual file extensions used across the source tree.
Prove the fix
- 01The same import statement no longer raises ERR_MODULE_NOT_FOUND on a fresh process start.
- 02A minimal reproduction script that imports the previously failing specifier exits with code 0.
- 03NODE_DEBUG=module output shows the specifier resolving to a path that exists on disk.
- 04`node --check` on the importing module returns without parse or resolution errors.
- 05`npm ls <package>` reports the expected installed version and no missing markers.
- 06Any CI job or test that previously failed at the import step now passes reliably.
Prevention and next steps
- •Always include the file extension in ESM relative imports, and update tooling that strips them.
- •Import only subpaths that the target package's "exports" field declares as public.
- •Install and rebuild from the lockfile so dependency layout is reproducible across environments.
- •Run the build and the runtime entry point in CI to catch resolution drift before deployment.
- •Keep the dependency tree shallow and prefer packages with stable, documented "exports" maps.
Safe commands and checks
node --version
node -e "import('./<relative-path>.js').then(()=>console.log('ok')).catch(e=>console.error(e.code,e.message))"
NODE_DEBUG=module node <entry>.mjs
npm ls <package>
ls -la <path-to-target>
node --check <file>.mjs
cat <package>/package.json
node --input-type=module --eval "import('<specifier>')"