Fix: Webpack Module Not Found – Can't Resolve '<module>' in '<directory>'
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix the Webpack error 'Module not found: Error: Can't resolve' caused by missing packages, wrong paths, aliases, or extension resolution issues.
The Error
You run your Webpack build or start a dev server and hit one of these:
Missing an npm package:
Module not found: Error: Can't resolve 'lodash'
in '/home/user/my-app/src'Wrong relative import path:
Module not found: Error: Can't resolve './components/Heder'
in '/home/user/my-app/src/pages'Missing file extension:
Module not found: Error: Can't resolve './utils/format'
in '/home/user/my-app/src'Node.js core module not available (Webpack 5):
Module not found: Error: Can't resolve 'crypto'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.Alias not resolving:
Module not found: Error: Can't resolve '@components/Button'
in '/home/user/my-app/src/pages'All of these share the same root message: Module not found: Error: Can't resolve. Webpack tried to locate a module based on the string you passed to import or require() and could not find a matching file or package anywhere in its resolution chain.
Why This Happens
Webpack resolves modules using its own resolution algorithm, which is similar to Node.js resolution but with additional features like aliases, extension fallbacks, and module directories. When you write import something from './path' or import pkg from 'package-name', Webpack walks through a series of steps:
- Is it a relative or absolute path? If the import starts with
./,../, or/, Webpack looks for a file at that path relative to the importing file. - Is it a package name? If there is no path prefix, Webpack searches
node_modulesdirectories, walking up the directory tree. - Does a
resolve.aliasmatch? If you configured aliases in your Webpack config, Webpack checks those mappings. - Does the file extension resolve? Webpack tries appending each extension listed in
resolve.extensions(by default.js,.json, and.wasmin Webpack 5). - Is it an
externalsentry? If the module is listed inexternals, Webpack skips bundling it and expects it to be available at runtime.
If every step fails, Webpack throws Module not found: Error: Can't resolve. The causes range from a missing npm install to a subtle alias misconfiguration or a case-sensitivity mismatch between operating systems.
Diagnostic Timeline
Here is the order an experienced engineer actually works through this error in production, including the wrong guesses that waste time and how to discard them quickly.
Minute 0 — first reaction: “node_modules is corrupted, just reinstall.”
You delete node_modules and package-lock.json, run npm install, and rebuild. The error returns identically. You spent five minutes confirming the install was already healthy. Discard this hypothesis: if the package name was wrong in package.json, the install itself would have failed. If the error references a relative path (./components/Heder), npm install was never going to help.
Minute 2 — second reaction: “missing dependency in package.json.”
You search the failing import name in package.json. If it is a third-party package, the listing is missing — install it and move on. But if the import is ./components/Button or @/utils/format, this hypothesis is wrong. Look at the path prefix: ./ or ../ means a file in your tree, @/ or @components/ means an alias, no prefix means a package. The fix path is completely different per case.
Minute 5 — third reaction: “the file extension is missing.”
You add .ts and .tsx to resolve.extensions (or to tsconfig-paths-webpack-plugin) and rebuild. If the build now passes locally but the CI build on Linux still fails with the same error, you have hit the real root cause: case sensitivity. macOS and Windows are case-insensitive by default, so import Header from './header' resolves on your laptop and fails in GitHub Actions on Ubuntu. This explains roughly 40% of “works on my machine” Webpack errors.
Minute 10 — actual root cause.
Run Webpack with full resolution logging to see the exact filenames it tried:
npx webpack --stats-error-details --stats-modules-space 999Or in webpack.config.js:
module.exports = {
stats: {
logging: 'verbose',
loggingDebug: [/resolve/],
},
};The output prints every candidate path Webpack walked. Look for one of three patterns:
- Mismatched case between import and filename — the import is
./Headerbut the file isheader.tsx. Fix the import or rename withgit mv(a casing-only rename is invisible to Git on macOS). - Alias resolves to a path that does not exist —
@componentsis configured inwebpack.config.jsbut the actual directory has been moved or renamed.tsconfig.jsonpaths may still point at the old location. For a deeper guide to alias-only failures see Fix: Webpack Alias Not Working. - Symlinked workspace package in a monorepo where Webpack follows the symlink and resolves dependencies from the linked location’s
node_modulesinstead of the project root.resolve.symlinks: falseor an explicitresolve.modulesentry fixes this.
For a separate class of bundler error where the file resolves but cannot be parsed, see Fix: Webpack Module Parse Failed Unexpected Token — that one shows up after Webpack found the file, not before.
Fix 1: Install the Missing npm Package
The most common cause is that the package simply is not installed. If the error names a package (not a relative path), install it:
npm install lodashIf you already have it in package.json, the install may have failed or node_modules may be out of sync:
rm -rf node_modules package-lock.json
npm installCheck whether the package is actually present:
ls node_modules/lodashIf you are using Yarn or pnpm, use the equivalent commands:
# Yarn
yarn install
# pnpm
pnpm installA common mistake is installing a package globally and expecting Webpack to find it. Webpack only looks in the local node_modules by default. Always install packages locally for projects that bundle with Webpack. If the install itself fails with dependency conflicts, see Fix: npm ERR! ERESOLVE unable to resolve dependency tree for resolving version conflicts.
Real-world scenario: You clone a teammate’s branch, run
npm start, and immediately get “Can’t resolve ‘styled-components’”. They added the package to their code but forgot to commit the updatedpackage.json. A quicknpm install styled-componentsfixes it, but always checkgit diffonpackage.jsonto confirm the dependency was actually added.
Fix 2: Correct the Relative Import Path
If the error references a relative path like ./components/Heder, you have a typo or the file structure does not match your import.
Verify the file exists at the expected location:
ls src/components/Header.jsxCommon mistakes:
- Typo in the file or directory name.
Hederinstead ofHeader,uitlsinstead ofutils. - Wrong nesting level. Using
./components/Buttonwhen the file is at../components/Buttonrelative to the importing file. - Importing a directory without an index file.
import App from './App'expects either./App.js(or another resolved extension) or./App/index.js.
Double-check the exact path by looking at your directory structure. The error message tells you the directory Webpack was searching from (the in '/path/to/dir' part), so you can reconstruct the full path it tried.
Fix 3: Add Missing Extensions to resolve.extensions
Webpack only auto-resolves certain file extensions. By default in Webpack 5, resolve.extensions is set to ['.js', '.json', '.wasm']. If you import a .ts, .tsx, .jsx, or .mjs file without specifying the extension, Webpack will not find it.
Add the extensions your project uses:
// webpack.config.js
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
},
};Now import Button from './Button' will match ./Button.tsx or ./Button.jsx without an explicit extension.
Order matters. Webpack tries extensions from left to right and uses the first match. If you have both Button.js and Button.tsx in the same directory, the one matching the earlier extension wins. Put your most common extension first.
A related problem occurs if you import a CSS or SCSS file and forget the extension. CSS files should always be imported with their extension (import './styles.css'), since adding .css to resolve.extensions can cause unexpected resolution conflicts. If Webpack chokes on the CSS file’s contents after resolving it, the issue is a missing loader instead — see Fix: Module parse failed: Unexpected token in Webpack for that.
Fix 4: Fix resolve.alias Misconfiguration
Webpack’s resolve.alias lets you create shorthand paths like @components or @utils. A broken alias is a frequent source of “Can’t resolve” errors.
The alias does not match the import
// webpack.config.js
const path = require('path');
module.exports = {
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
},
},
};With this configuration, import Button from '@components/Button' works. But import Button from '@/components/Button' does not — the alias is @components, not @/components. The prefix must match exactly.
The alias target path is wrong
The path you pass to path.resolve must point to an existing directory or file. A common error is using a relative path instead of an absolute one:
// Wrong -- relative path, will break depending on cwd
alias: {
'@src': './src',
}
// Correct -- absolute path
alias: {
'@src': path.resolve(__dirname, 'src'),
}Alias and TypeScript paths out of sync
If you use TypeScript, your tsconfig.json paths must mirror your Webpack aliases. Otherwise TypeScript may accept the import (no red squiggles in your editor) but Webpack cannot resolve it at build time:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}// webpack.config.js
const path = require('path');
module.exports = {
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
};Both sides must agree on the alias prefix and the target directory. If your TypeScript project shows module resolution errors at compile time as well, see Fix: Cannot find module or its corresponding type declarations in TypeScript for TypeScript-specific solutions.
Fix 5: Handle Case Sensitivity (Linux vs. macOS/Windows)
File systems on macOS and Windows are case-insensitive by default. import Header from './header' will find Header.jsx without issue on those platforms. On Linux, file systems are case-sensitive, so the same import fails because header and Header are different names.
This is a common cause of builds that pass locally but fail in CI/CD (which typically runs Linux). The fix is straightforward: match the exact casing of the file name in every import.
To catch case-sensitivity issues before they reach CI, use the case-sensitive-paths-webpack-plugin:
npm install --save-dev case-sensitive-paths-webpack-plugin// webpack.config.js
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
module.exports = {
plugins: [
new CaseSensitivePathsPlugin(),
],
};This plugin forces Webpack to error on case mismatches even on case-insensitive file systems, so you catch the problem during local development instead of discovering it in production.
Common Mistake: Renaming a file by only changing its casing (e.g.,
header.jsxtoHeader.jsx) may not register as a change in Git on macOS or Windows. Usegit mv header.jsx Header.jsxto ensure Git tracks the rename, otherwise your CI build on Linux will still see the old filename and fail.
Fix 6: Resolve Symlink Issues
Webpack follows symlinks by default (via resolve.symlinks, which defaults to true). This is usually fine, but it can cause problems in monorepos or when using npm link:
- Duplicate packages. If a symlinked package has its own
node_modules, Webpack may resolve a dependency from the symlinked location instead of the project root, leading to two copies of the same package or aCan't resolveerror when the expected package is not in the symlinked directory’snode_modules. - Resolution escaping the project. Webpack may follow a symlink to a directory outside your project, where
node_modulesdoes not exist.
Disable symlink resolution
// webpack.config.js
module.exports = {
resolve: {
symlinks: false,
},
};This tells Webpack to use the symlink path as-is instead of resolving to the real path. This keeps module resolution within the expected directory structure.
Add additional modules directories
If you are in a monorepo and packages cannot find shared dependencies, tell Webpack where to look:
// webpack.config.js
const path = require('path');
module.exports = {
resolve: {
modules: [
'node_modules',
path.resolve(__dirname, '../../node_modules'), // monorepo root
],
},
};Using npm link
When you npm link a local package for development, the linked package is a symlink in node_modules pointing to your local clone. If that linked package imports a dependency that is only installed in the main project’s node_modules, Webpack may fail to find it because it is resolving from the symlink target’s directory.
The fix is to add the main project’s node_modules to resolve.modules, or disable symlink resolution as shown above.
Fix 7: Fix externals Misconfiguration
Webpack’s externals option tells the bundler to skip certain modules and expect them to be available at runtime (e.g., loaded from a CDN or provided by the host environment). If you accidentally mark a module as external that is not actually available at runtime, you will get a runtime error. But if the externals configuration uses a regex or function that inadvertently matches imports you did not intend to exclude, Webpack may report “Can’t resolve” for those modules during the build.
Check your externals configuration
// webpack.config.js
module.exports = {
externals: {
react: 'React',
'react-dom': 'ReactDOM',
},
};This is fine — it only excludes react and react-dom. But a regex-based external can be overly broad:
// Dangerous -- excludes everything in node_modules
externals: [/node_modules/],This tells Webpack not to bundle any module whose path matches node_modules, which effectively skips all your dependencies. In a server-side build (for Node.js), this is sometimes intentional — but it requires that all those packages are available at runtime via Node.js resolution. In a browser build, this breaks everything.
If you are building for Node.js and want to externalize dependencies, use webpack-node-externals for proper handling:
npm install --save-dev webpack-node-externalsconst nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externals: [nodeExternals()],
};webpack-node-externals only externalizes packages listed in package.json, which is safer than a blanket regex.
Fix 8: Add Fallback Polyfills for Node.js Core Modules (Webpack 5)
Webpack 4 automatically included polyfills for Node.js core modules like crypto, stream, buffer, path, and os. Webpack 5 removed this behavior. If your code (or a dependency) imports a Node.js core module, Webpack 5 throws:
Module not found: Error: Can't resolve 'crypto'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.Option A: Provide polyfills via resolve.fallback
If you need the module’s functionality in the browser, install a polyfill and add a fallback:
npm install --save-dev crypto-browserify stream-browserify buffer process// webpack.config.js
const webpack = require('webpack');
module.exports = {
resolve: {
fallback: {
crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'),
buffer: require.resolve('buffer/'),
process: require.resolve('process/browser'),
path: require.resolve('path-browserify'),
os: require.resolve('os-browserify/browser'),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
zlib: require.resolve('browserify-zlib'),
url: require.resolve('url/'),
assert: require.resolve('assert/'),
util: require.resolve('util/'),
},
},
plugins: [
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser',
}),
],
};You only need to add fallbacks for the modules that your code actually uses. The error message will tell you which module is missing.
Option B: Set the fallback to false
If the code path using the Node.js module is never actually reached in the browser (e.g., it is behind a server-only conditional), you can tell Webpack to substitute an empty module:
resolve: {
fallback: {
fs: false,
net: false,
tls: false,
dns: false,
child_process: false,
},
}This silences the error and provides an empty object at runtime. The code will break if it actually tries to use these modules — but if the code path is unreachable in the browser, that is fine.
Option C: Use node-polyfill-webpack-plugin
If you have many polyfills to configure, the node-polyfill-webpack-plugin package adds all of them at once:
npm install --save-dev node-polyfill-webpack-pluginconst NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
module.exports = {
plugins: [
new NodePolyfillPlugin(),
],
};This is the fastest way to migrate a Webpack 4 project to Webpack 5 without rewriting every Node.js core module import. However, it increases your bundle size. Once things are working, audit which polyfills are actually needed and switch to explicit resolve.fallback entries. For Node.js runtime module errors outside of Webpack, see Fix: Error Cannot find module in Node.js.
Fix 9: Verify the Package’s exports and main Fields
Some packages use the exports field in their package.json to restrict which files can be imported. If a package exposes only specific entry points, importing a subpath that is not listed in exports will fail:
Module not found: Error: Can't resolve 'some-package/utils'Check the package’s package.json:
cat node_modules/some-package/package.jsonLook for an exports field. If /utils is not listed, the import is intentionally blocked by the package author. You may need to import from the main entry point and access the utility from there, or check if a different subpath is available.
You can configure Webpack to use a different condition for the exports field via resolve.conditionNames:
// webpack.config.js
module.exports = {
resolve: {
conditionNames: ['import', 'module', 'browser', 'default'],
},
};Some packages export different entry points for different conditions (import for ESM, require for CJS, browser for browser builds). Setting the right condition names ensures Webpack picks up the correct entry point.
Still Not Working?
Enable Webpack’s detailed resolution logging
Webpack can log every step of its module resolution process. This is extremely useful for understanding exactly why a module is not found:
// webpack.config.js
module.exports = {
stats: {
logging: 'verbose',
loggingDebug: [/webpack\.cache/, /resolve/],
},
};Or from the command line:
npx webpack --stats-reasons --stats-modules-space 999This will print out every path Webpack tried, which extensions it appended, and which aliases it checked. The output is verbose but tells you exactly where resolution diverged from your expectations.
Check for circular dependencies
Circular imports do not usually cause “Can’t resolve” directly, but they can create confusing partial-load scenarios where an exported value is undefined. If you are getting “Can’t resolve” on a module that clearly exists, circular dependencies may be causing the module to appear empty or uninitialized at the time it is imported. Use circular-dependency-plugin to detect them:
npm install --save-dev circular-dependency-pluginconst CircularDependencyPlugin = require('circular-dependency-plugin');
module.exports = {
plugins: [
new CircularDependencyPlugin({
exclude: /node_modules/,
failOnError: true,
}),
],
};Verify your tsconfig.json paths are wired to Webpack
If you use TypeScript path aliases and the build fails only in Webpack (not in tsc), make sure you have tsconfig-paths-webpack-plugin installed and configured:
npm install --save-dev tsconfig-paths-webpack-pluginconst TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
resolve: {
plugins: [new TsconfigPathsPlugin()],
},
};This plugin reads your tsconfig.json paths and registers them as Webpack aliases automatically, so you do not have to duplicate the configuration.
Check if ESLint or another tool is interfering
If your build pipeline runs ESLint before Webpack, an ESLint import resolution error can be mistaken for a Webpack error. Check your full build output carefully. If the error comes from ESLint’s import plugin rather than Webpack, see Fix: ESLint Parsing error: Unexpected token for ESLint-specific fixes.
Rebuild after config changes
Webpack caches resolved modules aggressively. If you changed your resolve configuration but the error persists, clear the cache:
rm -rf node_modules/.cache
npx webpackIf you are using cache: { type: 'filesystem' } in your Webpack config, the cache is stored on disk and survives restarts. Delete it to force a full rebuild:
rm -rf node_modules/.cache/webpackA pnpm workspace package fails after a fresh install
In a pnpm workspace, packages are linked from the global store via symlinks with strict isolation. A transitive dependency that worked under npm or Yarn may fail under pnpm because Webpack cannot find it without explicit hoisting. Add the missing package to the workspace’s package.json directly instead of relying on hoisting, or set shamefully-hoist=true in .npmrc for parity with npm’s flat layout. Set resolve.symlinks: false so Webpack resolves from the symlinked package’s own node_modules instead of escaping to the real path.
The error names a TypeScript path alias that exists in tsconfig
If tsc --noEmit succeeds but Webpack throws Can't resolve '@app/...', the TypeScript paths are not wired into the Webpack pipeline. tsconfig.json paths are a compile-time hint for tsc and your editor — they do not influence runtime module resolution. Either duplicate every alias in resolve.alias or install tsconfig-paths-webpack-plugin so a single source of truth drives both layers. For Node.js runtime resolution failures of the same packages outside the Webpack build, see Fix: Error Cannot find module in Node.js.
A package upgrade renamed an internal subpath
Recent ESM-first packages restrict deep imports via the exports field in their package.json. If import X from 'lib/internal/helpers' worked before and now fails after npm update, the package author removed the subpath from exports. Read the package’s CHANGELOG for the supported entry points or pin to the previous major version. Running into the broader npm dependency-tree issue during the rollback? See Fix: npm ERR ERESOLVE unable to resolve dependency tree.
Related:
- Fix: Module parse failed: Unexpected token in Webpack — for errors where the module is found but cannot be parsed.
- Fix: Error Cannot find module in Node.js — for Node.js runtime module resolution errors outside of Webpack.
- Fix: Cannot find module or its corresponding type declarations in TypeScript — for TypeScript-specific import resolution failures.
- Fix: npm ERR! ERESOLVE unable to resolve dependency tree — for npm dependency conflicts during installation.
- Fix: ESLint Parsing error: Unexpected token — for ESLint configuration issues that produce misleading import errors.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Error: error:0308010C:digital envelope routines::unsupported
How to fix the Node.js digital envelope routines unsupported error caused by OpenSSL 3.0 changes in Node.js 17+, with solutions for webpack, Vite, React, and Angular.
Fix: Node.js fs.watch Not Working — Cross-Platform Quirks, chokidar Migration, Recursive Watch
How to fix Node.js file watching — fs.watch unreliable on Linux, missing events on save, recursive watch on Windows/macOS, chokidar polling fallback, ignoring patterns, debouncing, and EMFILE errors.
Fix: Node.js Stream pipeline() Not Working — Backpressure, Error Propagation, AbortSignal, and Web Streams Interop
How to fix Node.js stream/promises pipeline errors — uncaught stream errors, backpressure ignored, AbortSignal not propagating, async iterators in pipeline, Transform stream object mode, and converting between Node and Web Streams.
Fix: Node.js Test Runner Not Working — node --test, TypeScript, Mocks, Coverage, and Watch Mode
How to fix Node.js built-in test runner errors — node --test not finding files, ESM vs CJS imports, TypeScript with --experimental-strip-types, mock.method isolation, coverage reporting, and watch mode setup.