Diagnosing 'Module not found' Errors in Webpack Resolvers
A diagnostic guide to Webpack 'Module not found' errors: read the resolver probe list, then fix extensions, aliases, file case or module roots.
10 Nov 2025, 11:33 UTC

Why the resolver says the module is missing
Webpack does not treat an import string as a file path. It hands each request to an enhanced-resolve instance (the resolver), which probes a list of directories and file extensions until something matches. A Module not found error means every probe failed — not necessarily that the file is absent.
The fastest way out is to make the resolver print its probe list, then compare the failing probe against your config. Guessing at aliases and extensions usually costs more time than reading the probe log.
Match the symptom to the config key
| Symptom | Config key | What to inspect |
|---|---|---|
Extensionless import of .ts, .tsx or .jsx fails |
resolve.extensions |
Array contents and order |
Import starting with @ or ~ fails |
resolve.alias |
Alias key versus import prefix |
| Build passes locally, fails on Linux CI | File system case | Import string versus ls output |
| Package not found in a monorepo | resolve.modules |
Hoisted node_modules locations |
Works only after npm link |
resolve.symlinks |
Real path versus symlink path |
Ordered checks
1. Print the resolver probe list
Run from the project root, using the same Node version as the failing build. In webpack 4 and earlier:
npx webpack --display-error-details
That CLI flag was removed in webpack 5. There, enable the same detail through the stats option instead:
// webpack.config.js
module.exports = {
stats: { errorDetails: true }
};
Expected output: a list of absolute paths the resolver tried, including each extension it appended. Read the last few entries — they show the directory and extension combination closest to a match, which tells you whether the problem is the directory or the extension.
2. Fix extension mismatches
If you import without an extension, the resolver only finds the file when its extension appears in resolve.extensions. Webpack 5 defaults to ['.js', '.json', '.wasm'], so a .ts or .tsx import fails until you add it.
resolve: {
extensions: ['.tsx', '.ts', '.jsx', '.js', '.json']
}
Order matters: the resolver probes left to right, so put the most specific format first. Every extra extension adds file system probes for imports that never resolve, so keep the list to formats the project actually uses.
3. Fix alias mismatches
Aliases must exist in two places: webpack for the build, and tsconfig.json or jsconfig.json paths for the editor. When only the editor config has them, the IDE reports no error and the build still fails.
const path = require('path');
module.exports = {
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components')
}
}
};
Check the key character by character. The alias @components matches @components/Button but not @component/Button. Point the alias at a directory without a trailing slash and let the resolver append the remainder.
4. Confirm case on case-sensitive file systems
macOS and Windows default to case-insensitive file systems; most Linux CI images are case-sensitive. Run this on the machine or in the container that fails:
ls src/components/
Compare the exact casing to the import string. If the import says ./user and the listing shows User.js, rename the import — or the file, using git mv so the rename is tracked — until they match.
5. Check module search roots
In a workspace where dependencies are hoisted to the repository root, the resolver may not look there by default. Add the root explicitly:
resolve: {
modules: [path.resolve(__dirname, 'node_modules'), 'node_modules']
}
Escalate when the probe list already looks correct
- Symlinked packages. With
npm linkor a monorepo symlink, tryresolve.symlinks: falseso the resolver reports the real path instead of the link path. - Filesystem cache. If
cache: { type: 'filesystem' }is enabled, deletenode_modules/.cache/webpackand rebuild before concluding a fix failed. - Package exports map. A dependency that ships an
exportsfield can block deep imports even though the file exists on disk. Check that package'spackage.json.
If the probe list shows the correct absolute path and extension and the error persists, the cause is probably outside the resolver — a loader rule, a plugin, or an exports map. Escalate with the full probe output attached rather than adding more aliases.
Verify the fix
Re-run the build and confirm a zero exit code with no Module not found lines. Then repeat the command in the environment that originally failed — the CI container or Linux host — because case and hoisting differences are exactly what checks 4 and 5 target.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.