nextjsseverity: blocker
Module not found: Can't resolve

Next.js: Module not found: Can't resolve 'X'

Module not found

100% fixable~5 mindifficulty: beginner

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.

> advertisementAdSense placeholder

How to fix it

  1. 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'
  2. 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
  3. 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'
  4. 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'
  5. 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'
  6. 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.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is manually verified against official sources listed in the “sources” sidebar. If a fix here didn’t work for you, please email so we can update the page.