Fix: Module not found: Can't resolve / Cannot find module or its corresponding type declarations
The Error
You run your build or start your dev server and get one of these:
Webpack (Create React App, Next.js, or custom config):
Module not found: Error: Can't resolve 'some-package' in '/home/user/my-app/src'Module not found: Error: Can't resolve './components/Header' in '/home/user/my-app/src'Vite:
[vite] Internal server error: Failed to resolve import "some-package" from "src/App.tsx". Does the file exist?TypeScript:
error TS2307: Cannot find module './components/Header' or its corresponding type declarations.Generic bundler error:
Module not found: Can't resolve '@/utils/helpers' in '/home/user/my-app/src/pages'All of these mean the same thing: your bundler or compiler tried to follow an import and couldn’t find the file or package it points to. The causes range from a missing npm install to a subtle path casing mismatch that only breaks on Linux.
Why This Happens
When you write import Something from 'somewhere', your bundler has to map that string to a real file on disk. This resolution process checks multiple locations in a specific order: node_modules, path aliases, relative file paths, and file extensions. When nothing matches, you get “Module not found.”
The most common causes:
- The package isn’t installed. You added an import but never ran
npm install, or the package is missing frompackage.json. - The import path has a typo or wrong casing. Works on macOS/Windows (case-insensitive), fails on Linux (case-sensitive). This is the number one “works locally, breaks in CI” bug.
- A path alias isn’t configured in the bundler. TypeScript
pathsintsconfig.jsononly affect type checking. Webpack and Vite need their own alias configuration. - A file extension is missing. Some setups require explicit extensions; others resolve them automatically.
node_modulesis corrupted or out of sync. A failed install, a branch switch, or a stale lockfile left behind broken or missing packages.- A barrel file re-export is broken. An
index.tsre-exports from files that have been moved, renamed, or deleted. - You’re importing a CSS, image, or asset file without the right loader or plugin. Bundlers need explicit configuration to handle non-JavaScript imports.
Fix 1: Install the Missing Package
If the error names an npm package (not a relative path), install it:
npm install some-packageIf it’s a dev dependency:
npm install --save-dev some-packageIf you just cloned the repo or switched branches, install everything:
npm installCheck whether the package is listed in package.json but missing from node_modules:
npm ls some-packageMissing @types/ packages
If TypeScript reports Cannot find module or its corresponding type declarations, the runtime package may be installed but its types are missing. Install the corresponding @types/ package:
npm install --save-dev @types/react @types/react-dom @types/nodeNot all packages need a separate @types/ install. Packages like axios, zod, and date-fns bundle their own types. Check the package’s package.json for a types or typings field — if it’s there, the package ships its own types.
For more on TypeScript-specific type resolution issues, see Fix: TS2307 Cannot find module or its corresponding type declarations.
Fix 2: Fix the Import Path
A wrong path is the most common cause of this error after a missing package. Check these in order.
Typos
Read the error message carefully. It shows the exact import string that failed. Compare it character by character to the actual file name on disk:
// File on disk: src/components/UserProfile.tsx
// Wrong
import UserProfile from './components/UserProfle'; // typo: "Profle"
// Correct
import UserProfile from './components/UserProfile';Case sensitivity
On macOS and Windows, the file system is case-insensitive by default. ./components/header resolves to Header.tsx without issues. On Linux — which is what most CI/CD pipelines and production servers run, including Docker containers — it doesn’t.
// File on disk: src/components/Header.tsx
// Works on macOS/Windows, FAILS on Linux
import Header from './components/header';
// Correct everywhere
import Header from './components/Header';Always match the exact casing. This prevents the “it works on my machine” class of bugs.
Relative vs. absolute imports
Paths starting with ./ or ../ are relative to the current file. Paths without a prefix are treated as packages in node_modules (or path aliases, if configured):
// Looks in node_modules for a package called "utils/helpers"
import { format } from 'utils/helpers';
// Looks for the file relative to the current file
import { format } from './utils/helpers';If you mean to import a local file, always start with ./ or ../.
Missing file extensions
Most bundlers resolve .js, .jsx, .ts, .tsx, and .json extensions automatically. You can usually omit them:
// Both work in webpack and Vite
import Header from './components/Header';
import Header from './components/Header.tsx';But if you’re importing a file type the bundler doesn’t resolve by default (.mjs, .cjs, .vue, .svelte), you must include the extension. In Webpack, you can extend the list:
// webpack.config.js
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.vue'],
},
};In Vite:
// vite.config.ts
export default defineConfig({
resolve: {
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
});Index file resolution
When you import a directory, bundlers look for an index file inside it:
// Resolves to ./components/index.ts (or .tsx, .js, etc.)
import { Button } from './components';If the directory has no index file, you’ll get “Module not found.” Either create one or import the specific file:
import { Button } from './components/Button';Fix 3: Configure Path Aliases
Path aliases like @/components/Header are convenient but require configuration in both your TypeScript config and your bundler. A mismatch between the two is a frequent cause of this error.
Webpack (resolve.alias)
// webpack.config.js
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
};Or use tsconfig-paths-webpack-plugin to read aliases from tsconfig.json automatically:
npm install --save-dev tsconfig-paths-webpack-plugin// webpack.config.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
module.exports = {
resolve: {
plugins: [new TsconfigPathsPlugin()],
},
};Vite (resolve.alias)
// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});Or use vite-tsconfig-paths to sync with tsconfig.json:
npm install --save-dev vite-tsconfig-paths// vite.config.ts
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [tsconfigPaths()],
});TypeScript (tsconfig.json paths)
TypeScript’s paths option tells the compiler where to find aliased modules during type checking only. It does not affect runtime resolution:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}You need both the TypeScript config and the bundler config. TypeScript paths alone won’t make imports work at build time. And bundler aliases alone won’t make TypeScript’s type checker happy.
For a deeper look at Vite-specific alias issues, see Fix: Failed to resolve import in Vite.
Fix 4: Delete node_modules and Reinstall
A corrupted or stale node_modules directory causes phantom “Module not found” errors. The package appears in package.json but something is broken on disk — a partially downloaded package, a dangling symlink, or a leftover from a different branch.
Nuclear option — delete everything and reinstall:
rm -rf node_modules package-lock.json
npm installOn Windows (Command Prompt):
rmdir /s /q node_modules
del package-lock.json
npm installIf you’re using Yarn:
rm -rf node_modules yarn.lock
yarn installpnpm:
rm -rf node_modules pnpm-lock.yaml
pnpm installNote: Deleting the lockfile forces a full dependency re-resolution. In most cases this is fine for local development. In CI or production, you typically want to keep the lockfile and use npm ci instead. If you hit permission errors during reinstall, see Fix: npm ERR! EACCES permission denied.
Clear bundler caches too
After reinstalling node_modules, also clear any bundler cache:
# Vite
rm -rf node_modules/.vite
# Webpack (CRA)
rm -rf node_modules/.cache
# Next.js
rm -rf .next
# General
rm -rf dist buildFix 5: Fix CSS, Image, and Asset Imports
Importing non-JavaScript files throws “Module not found” unless your bundler is configured to handle them.
CSS imports
Webpack needs css-loader and style-loader:
npm install --save-dev css-loader style-loader// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
};Vite handles CSS imports natively — no config needed. If you’re getting “Module not found” for a CSS import in Vite, the file path is wrong, not the configuration.
CSS Modules
If you’re using CSS Modules (files named *.module.css), the import syntax differs:
// Regular CSS — side effect import
import './styles.css';
// CSS Modules — named import
import styles from './Button.module.css';
// Then use it
<div className={styles.container}>Create React App and Vite support CSS Modules out of the box. For custom Webpack configs, you need css-loader with the modules option enabled.
Image and asset imports
Webpack needs file-loader or asset/resource (Webpack 5):
// webpack.config.js (Webpack 5)
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset/resource',
},
],
},
};Vite handles static assets natively. Import them directly:
import logo from './assets/logo.png';
// logo is a URL string: "/assets/logo.abc123.png"TypeScript declarations for assets
TypeScript doesn’t know how to type CSS or image imports. You’ll get TS2307: Cannot find module './logo.png' or its corresponding type declarations. Create a declaration file:
// src/types/assets.d.ts
declare module '*.css' {
const classes: Record<string, string>;
export default classes;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}If you’re using Vite, add /// <reference types="vite/client" /> in a .d.ts file or add "types": ["vite/client"] in tsconfig.json. Vite’s client types handle all common asset types.
Fix 6: Fix Barrel File Re-exports
Barrel files (index.ts files that re-export from other modules) are a common source of “Module not found” errors. When a referenced file is moved, renamed, or deleted, the barrel file’s re-export breaks silently until someone imports from it.
// src/components/index.ts (barrel file)
export { Button } from './Button';
export { Header } from './Header';
export { Footer } from './Footer'; // Footer.tsx was deleted — Module not foundFix: Check every re-export in the barrel file. Make sure each referenced file exists and the path is correct. Remove or update exports that point to files that no longer exist.
Tip: Some projects avoid barrel files entirely because they can slow down bundling, cause circular dependency issues, and break tree shaking. If you’re hitting frequent problems with barrel files, consider importing directly from the source file instead:
// Instead of
import { Button } from '@/components';
// Import directly
import { Button } from '@/components/Button';Fix 7: Monorepo Hoisting Issues
In monorepos using npm workspaces, Yarn workspaces, or pnpm workspaces, “Module not found” often means a dependency isn’t properly declared in the workspace package that needs it.
The phantom dependency problem
With npm and Yarn, dependencies are hoisted to the root node_modules by default. This means a package installed for workspace A can accidentally be importable from workspace B — even though workspace B doesn’t list it as a dependency. When the dependency tree changes and that package stops being hoisted, workspace B breaks.
Fix: Always declare every dependency in the package.json of the workspace that uses it:
{
"name": "@myorg/web-app",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"@myorg/shared": "workspace:*"
}
}pnpm strict isolation
pnpm doesn’t hoist by default. Each workspace package only sees its own declared dependencies. This is stricter but catches missing dependency declarations early.
If you’re getting “Module not found” after migrating to pnpm, you likely have packages that relied on hoisting. Declare the missing dependencies explicitly in each workspace’s package.json.
Symlink resolution
Some bundlers don’t follow symlinks correctly (which is how workspaces link packages). In Webpack, you may need:
// webpack.config.js
module.exports = {
resolve: {
symlinks: false,
},
};In Vite, set preserveSymlinks:
// vite.config.ts
export default defineConfig({
resolve: {
preserveSymlinks: true,
},
});For dependency tree resolution errors during install, see Fix: npm ERR! ERESOLVE unable to resolve dependency tree.
Edge Cases
exports field blocking deep imports
Modern packages use the exports field in package.json to restrict which subpaths can be imported:
{
"name": "some-library",
"exports": {
".": "./dist/index.js",
"./utils": "./dist/utils.js"
}
}// Works — mapped by "."
import someLib from 'some-library';
// Works — mapped by "./utils"
import { helper } from 'some-library/utils';
// Fails — not in exports map
import { internal } from 'some-library/dist/internal.js';This is intentional. The package author chose not to expose internal files. If you need access, check if the package offers an alternative export path, or file an issue on the package’s repo.
Dynamic imports with variable paths
Bundlers can’t resolve imports where the path is a runtime variable:
// Webpack and Vite can't resolve this at build time
const module = await import(modulePath);Use explicit paths or the bundler’s pattern-based dynamic import:
// Webpack — use a partial path so webpack can create a chunk for each possible match
const module = await import(`./modules/${moduleName}.js`);Circular dependencies causing partial imports
Circular imports (A imports B, B imports A) don’t always throw “Module not found” directly, but they can cause exports to be undefined at the time they’re accessed. This results in confusing errors that look like missing modules.
Detect circular dependencies:
npx madge --circular src/If you also have ESLint parsing errors alongside module resolution failures, fix the parser configuration first — ESLint misconfiguration can mask the real import errors.
Webpack resolve.modules misconfiguration
Webpack’s resolve.modules tells the bundler which directories to search for modules. Create React App sets this to ["node_modules", "src"], which is why bare imports like import Header from 'components/Header' work in CRA but not in other setups.
If you’re seeing “Module not found” after ejecting from CRA or migrating away from it, check if your imports rely on resolve.modules:
// webpack.config.js
module.exports = {
resolve: {
modules: ['node_modules', 'src'],
},
};Warning: Using resolve.modules with 'src' means bare imports like import x from 'utils' could match either a package in node_modules or a file in src/utils. This creates ambiguity. Path aliases are a cleaner approach.
Different tsconfig files for different files
Large projects often have multiple TypeScript configs (tsconfig.json, tsconfig.app.json, tsconfig.node.json). If an import resolves in one config but not another, check which config governs the file throwing the error.
In a Vite project, tsconfig.app.json typically covers src/ files and tsconfig.node.json covers vite.config.ts. Adding a path alias to one doesn’t affect files governed by the other.
Still Not Working?
Restart the dev server
Bundlers cache module resolution results. After installing packages, changing config files, or modifying path aliases, restart the dev server:
# Kill the running process and start fresh
npm run devThis sounds obvious, but many resolution issues persist simply because the dev server is using stale state.
Verify the file exists on disk
Double-check that the file you’re importing actually exists where you think it does:
ls src/components/Header.tsxIf you’re on a case-sensitive file system, verify the exact casing:
ls src/components/ | grep -i headerCheck for invisible characters in file names
Copy-pasting file names from documentation or chat apps can introduce invisible Unicode characters (zero-width spaces, non-breaking spaces). These make the file appear normal in your editor but fail to resolve at build time.
Rename the file by typing the name manually instead of pasting it.
Check NODE_PATH
If NODE_PATH is set in your environment, it affects module resolution. An incorrect NODE_PATH can cause unpredictable behavior:
echo $NODE_PATHUnless you have a specific reason to set it, clear it:
unset NODE_PATHCheck .gitignore isn’t hiding files
If a file was added to .gitignore (like generated code or compiled output), it won’t exist after a fresh git clone. Make sure files your imports depend on are either tracked in git or generated during the build process.
VS Code showing errors but build works (or vice versa)
VS Code runs its own TypeScript server, which can get out of sync with your actual build. If VS Code shows “Cannot find module” but the build succeeds:
- Open the command palette (
Ctrl+Shift+P/Cmd+Shift+P). - Run “TypeScript: Restart TS Server”.
If the build fails but VS Code shows no errors, check which tsconfig.json each is using. VS Code might be reading a different config than your build command.
Related:
- Fix: Error Cannot find module in Node.js — for Node.js runtime
MODULE_NOT_FOUNDerrors. - Fix: TS2307 Cannot find module or its corresponding type declarations — for TypeScript-specific type resolution issues.
- Fix: Failed to resolve import in Vite — for Vite-specific import resolution errors.
- Fix: npm ERR! ERESOLVE unable to resolve dependency tree — for dependency installation conflicts.
Related Articles
Fix: Loading chunk failed / ChunkLoadError
How to fix 'Loading chunk failed', 'ChunkLoadError', and 'Failed to fetch dynamically imported module' in webpack, Next.js, React, and Vite. Covers stale deployments, CDN caching, publicPath misconfiguration, service worker cache, code splitting, dynamic import retry strategies, React.lazy error boundaries, and Next.js-specific solutions.
Fix: [vite] Internal server error: Failed to resolve import
How to fix Vite's 'Failed to resolve import' error, including 'Does the file exist?', 'Optimized dependency needs to be force included', 'Pre-transform error', and '504 (Outdated Optimize Dep)'. Covers missing packages, path aliases, optimizeDeps, cache clearing, and CJS/monorepo edge cases.
Fix: Module parse failed: Unexpected token (Webpack / Vite / esbuild)
How to fix 'Module parse failed: Unexpected token' in Webpack, Vite, and esbuild by configuring the correct loaders and transforms for JSX, TypeScript, CSS, JSON, and other file types.
Fix: TS2322 Type 'X' is not assignable to type 'Y'
How to fix TypeScript error TS2322 'Type is not assignable to type'. Covers literal types vs general types, string vs String, union types, interface compatibility, generic constraints, readonly arrays, excess property checking, discriminated unions, type assertions, type widening and narrowing, React event handlers, Promise return types, and enum mismatches.