Next.js: Module not found: Can't resolve 'X'
Module not found
Verified against Next.js docs: Module Resolution, Webpack docs: resolve, TypeScript docs: paths · Updated May 2026
> quick_fix
The bundler can't resolve the import path. Three causes: package not installed, wrong relative path, or tsconfig paths alias misconfigured. Check pnpm list for the package, verify the file exists at that exact case-sensitive path, and confirm tsconfig.json paths match next.config.js.
# 1. Is it installed?
pnpm list next # or whatever the missing package is
# 2. Does the file exist with exact case?
ls -la src/components/Header.tsx # not header.tsx on case-sensitive FS
# 3. tsconfig paths alias
cat tsconfig.json | grep -A 5 'paths'What causes this error
Next.js (via Turbopack in dev or webpack in production) walks the import statement and tries to resolve the module path. If the path doesn't match a file or installed package, the build fails with this error. Common triggers: forgotten npm install, case-sensitivity drift (works on macOS, fails on Linux), missing tsconfig paths alias, or import path that includes the file extension webpack doesn't expect.
How to fix it
- 01
step 1
Read the exact import path that failed
The error names the import like "Can't resolve '@/components/Header'". This is the literal string from your code. The fix has to match that string.
Module not found: Can't resolve '@/components/Header' ./app/page.tsx:3:1 > import { Header } from '@/components/Header' - 02
step 2
If it's a package, confirm install
Bare specifiers like 'lodash' or '@radix-ui/react-dialog' need to be in package.json AND installed in node_modules. Run pnpm install. Check that the package name in your import matches package.json exactly.
# Check package.json grep -E '"package-name"' package.json # Confirm in node_modules ls node_modules/package-name # Reinstall rm -rf node_modules pnpm-lock.yaml pnpm install - 03
step 3
If it's a relative path, check exact filename and case
macOS filesystems are case-insensitive by default; Linux is not. './components/Header' resolves on Mac even if the file is './components/header.tsx' but fails on Linux deploys. Always match case.
# List with exact case ls -la app/components/ # Header.tsx (capital H) # Your import must match import { Header } from './components/Header' // not 'header' - 04
step 4
If using @/ alias, check tsconfig and next.config
The @/ alias is purely a tsconfig.json paths convention. Next.js auto-reads it, but if you have a custom webpack/turbopack alias, both must agree. Mismatch fails resolution.
// tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } } } // imports must use the alias import { Header } from '@/components/Header' - 05
step 5
Check for accidental file-extension in import
Some bundler configs accept import './foo.ts'; Next.js by default does NOT - it expects no extension or .js. Strip the extension or check experimental.typedRoutes config.
// Bad in Next.js by default import { x } from './module.ts' // Good import { x } from './module' - 06
step 6
If using a workspace package, check tsconfig references
In monorepos (pnpm workspaces, Turborepo), shared packages must be listed in package.json dependencies AND tsconfig.json references must point at them. Missing either breaks resolution.
// apps/web/package.json { "dependencies": { "@repo/ui": "workspace:*" } } // apps/web/tsconfig.json { "references": [{ "path": "../../packages/ui" }] }
Why Module not found: Can't resolve happens at the runtime level
Module resolution in Next.js follows the Node-resolution algorithm extended with bundler-specific features. Turbopack (or webpack pre-v15) walks: tsconfig paths aliases, then node_modules upward, then relative paths. For each candidate it tries the literal path, then with .ts/.tsx/.js/.jsx extensions, then /index variants. If none match, the bundler emits the Module not found error and aborts the build. Case sensitivity is filesystem-dependent: HFS+ and APFS treat 'Header' and 'header' as the same name; ext4 treats them as distinct. This is why deployments to Linux containers expose case bugs that worked on Mac.
Common debug mistakes for Module not found: Can't resolve
- Importing 'Header' when the file is named 'header.tsx' on disk - works on Mac, fails on Linux deploy.
- Forgetting to install a new package after adding an import; the import shows up in source but pnpm install never ran.
- Adding a tsconfig paths alias but not restarting the dev server; turbopack caches the old config.
- Using a relative path that's correct from the source file but writing it as if from project root.
- Including a .ts extension in the import (Next.js typedRoutes config-dependent), which triggers resolution failure.
When Module not found: Can't resolve signals a deeper problem
Frequent 'Module not found' errors signal a missing import-discipline layer. The architectural fix is to enable forceConsistentCasingInFileNames in tsconfig, configure ESLint's import/no-unresolved rule, and run a pre-commit hook that catches case mismatches before push. In monorepos, declare every cross-package import via workspace: protocol and add references in tsconfig. With these in place, the IDE catches resolution errors before compile, and CI catches the rare slips. Without them, every team pushes case-sensitivity bugs to staging and discovers them only on the Linux build.
Frequently asked questions
Why does my code work locally but fail to build on Vercel/Railway?
Almost always case-sensitivity. macOS HFS+ and APFS are case-insensitive by default, so 'Header.tsx' and 'header.tsx' both resolve to the same file. Linux ext4 (used in Vercel/Railway containers) is case-sensitive: a wrong case import fails. Fix by renaming files and imports to match exactly. To prevent recurrence, set 'forceConsistentCasingInFileNames: true' in tsconfig.json so TypeScript catches the mismatch in dev. Check git: if the case was changed but git tracks it as the same name, run git mv -f OldName.tsx NewName.tsx to force a rename in version control.
What's the difference between Module not found and Cannot find module?
'Module not found' (bundler error from webpack/turbopack) fires at build time when resolution fails. 'Cannot find module' (TypeScript error TS2307) fires at type-check time when the compiler can't find type definitions. They have different fixes: bundler errors need package install or path correction; TypeScript errors need a types package (@types/node) or a custom .d.ts declaration. They often appear together - if both fire, fix the import path first, then types.
Why does Next.js suggest installing 'critters' or 'sharp' specifically?
These are optional dependencies for built-in features: critters for inline-CSS optimization, sharp for image optimization. Next.js prints 'Module not found: Can't resolve sharp' when a feature requires them but they're absent. Run pnpm install sharp to enable image optimization in production. On Vercel, sharp is auto-installed; on self-hosted, you must add it explicitly. Same pattern for any feature that's behind an optional peer dependency.
Can the @/ alias point at multiple directories?
Yes. tsconfig.json's paths field accepts arrays: '@/*': ['./src/*', './lib/*']. The compiler tries each in order and uses the first match. This is useful for monorepos where shared types live in /lib and app code lives in /src. Next.js respects this without extra config. Be careful: if two directories have a file with the same name, the first-listed wins, which can cause confusing 'wrong file imported' bugs. Prefer one path per alias when possible.