Skip to content

Diagnosing ERR_MODULE_NOT_FOUND after upgrading Node

Node's ESM resolver rules around mandatory file extensions and 'exports' enforcement are not new in Node 22 — the summary is important here: the upgrade is usually the trigger only because it came with a lockfile/dependency refresh, not because Node itself changed how it resolves modules. Splits the failing specifier into your own relative import (missing extension) versus a dependency (its exports map tightened). It does not cover a plain 'Cannot find module' with no ERR_ prefix right after npm install — that's a CommonJS-style resolution failure covered by a separate playbook.

Unverifiedno reproductions yetWhy this confidence?
Revision 1published by DevYou curationrevision history
Run the diagnosisEvidence and compatibility

Symptoms

The diagnostic path

6 steps. Every step is written out below in full — the interactive version simply follows the branches for you.

  1. Step 1 · StartERR_MODULE_NOT_FOUND after upgrading Node

    Node's mandatory-extension and package.json 'exports' rules under "type": "module" have been in place since ESM support landed, well before Node 20 or 22 — the version upgrade is usually the trigger only because it shipped with a dependency/lockfile update, not because Node's own resolution rules changed.

    What happens next

    • passed step 2, Does the failing specifier point at your own file, or an installed package?
  2. Step 2 · TestDoes the failing specifier point at your own file, or an installed package?

    Read the error's 'Cannot find module ...' line: a specifier starting with ./ or ../ is your own code; a bare name is a dependency.

    Read-onlysh
    # read the exact specifier from the error text

    Expected result

    Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/src/utils' imported from /app/src/index.js

    What happens next

    • passed step 3, Fix: add the required file extension to the relative import
    • failed step 4, Check the dependency's package.json 'exports' field for the path being imported
    • unknown step 4, Check the dependency's package.json 'exports' field for the path being imported
  3. Step 3 · FixFix: add the required file extension to the relative import

    Node's ESM loader requires the extension on relative specifiers — import { thing } from './utils' fails under "type": "module" even if it worked under CommonJS or a bundler that resolved extensions for you. Change it to './utils.js' (or whatever the actual compiled output extension is). If this previously ran through a build/transpile step that isn't running any more after the upgrade, that step disappearing is the real trigger, not Node 22 itself.

  4. Step 4 · TestCheck the dependency's package.json 'exports' field for the path being imported

    A newer version of the dependency may have added or tightened an 'exports' map, which blocks any import path not explicitly listed — including deep imports that used to work.

    Read-onlysh
    cat node_modules/<package-name>/package.json | grep -A20 '"exports"'

    Expected result

    "exports": {
      ".": "./index.js",
      "./package.json": "./package.json"
    }

    What happens next

    • passed step 5, Root cause: the dependency's exports map no longer allows this import path
    • failed step 6, The exports map isn't blocking this
    • unknown step 6, The exports map isn't blocking this
  5. Step 5 · Root causeRoot cause: the dependency's exports map no longer allows this import path

    The subpath being imported genuinely isn't listed in the package's 'exports' map. Import only the paths it actually exposes (usually the package root or a documented subpath), or pin the dependency to the last version before 'exports' was added or tightened if the internal path is genuinely required.

  6. Step 6 · EndThe exports map isn't blocking this

    The path being imported is listed in 'exports', or the package has no 'exports' field at all. Check instead whether the package actually installed correctly for this Node version (npm ls <package-name> for peer-dependency warnings), and whether two copies of it exist at different versions in node_modules, where a stale nested copy can shadow the top-level one.

Sources

Why this confidence?

What would strengthen it: 6 more independent reproductions. Reproductions from 3 more distinct environments.