LEARN · DEBUGGING GUIDE

TypeScript Path Alias 'Cannot Find Module' Errors

Path alias errors usually mean your tsconfig paths aren't aligned with your bundler or runtime. Here's how to reconcile them.

IntermediateTypeScript7 min read

What this usually means

The tsconfig.json `paths` mapping defines how the TypeScript compiler resolves module names during type-checking, but the runtime environment (Node.js, Webpack, Vite, Jest) uses its own module resolution. When you see 'Cannot find module', it's almost always a mismatch between the TypeScript compiler's understanding of the alias and the actual module resolution used during compilation or execution. Common causes: forgetting to configure the bundler (e.g., Webpack resolve.alias, Vite resolve.alias), missing baseUrl, incorrect path pattern (e.g., missing `/*` glob), or the runtime resolver not being told about the aliases at all (e.g., Node.js with ESM).

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Check tsconfig.json: verify 'baseUrl' is set (usually '.' or './src') and 'paths' uses '/*' globs e.g. '@/*': ['./src/*']
  • 2Run `npx tsc --noEmit` - if TS compiles fine, the issue is with your bundler/runtime, not TS
  • 3Check your bundler config: Webpack resolve.alias, Vite resolve.alias, Next.js tsconfig paths plugin, etc.
  • 4For Node.js runtime, check if you're using ts-node, tsx, or node with --experimental-specifier-resolution=node and whether the loader resolves aliases
  • 5Clear caches: delete node_modules/.cache, dist, and .tsbuildinfo, then rebuild
  • 6Check the actual resolved path by adding a temporary console.log(require.resolve('@/some/module')) in Node.js
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchtsconfig.json - the paths and baseUrl properties
  • searchwebpack.config.js or next.config.js for resolve.alias
  • searchvite.config.ts for resolve.alias
  • searchjest.config.js or vitest.config.js for moduleNameMapper
  • searchpackage.json exports field if using Node.js ESM
  • search.babelrc or babel.config.js for babel-plugin-module-resolver
  • searchtsconfig paths in IDE settings (e.g., VS Code js/ts.implicitProjectConfig.checkJs)
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningMissing baseUrl in tsconfig.json while using paths
  • warningIncorrect glob pattern: '@/*' maps to ['./src/*'] but baseUrl is '.' so path resolves to './src/*' correctly only if baseUrl is '.'
  • warningBundler alias not defined: Webpack/Vite/Next.js not configured with the same aliases as tsconfig
  • warningRelative paths in paths mapping: using './src/*' instead of 'src/*' when baseUrl is '.'
  • warningCase sensitivity on Linux: file is 'MyModule.ts' but import uses './my-module'
  • warningUsing path aliases in compiled JavaScript but runtime (Node) doesn't know about them
  • warningMultiple tsconfig files with conflicting paths (e.g., tsconfig.json vs tsconfig.build.json)
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildSet baseUrl to '.' and paths as '@/*': ['src/*'] (no leading './' for directory)
  • buildAdd resolve.alias in webpack.config.js: '@': path.resolve(__dirname, 'src')
  • buildIn Vite, add resolve.alias: { '@': '/src' } to vite.config.ts
  • buildFor Jest, add moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' }
  • buildFor Next.js, use the tsconfig-paths-webpack-plugin or Next.js built-in support (works if tsconfig paths are correct)
  • buildUse tsconfig-paths/register for ts-node or tsx to resolve aliases at runtime
  • buildIf using Babel, install babel-plugin-module-resolver and configure alias
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedRun `npx tsc --noEmit` with no errors
  • verifiedBuild the project and check the output bundle for correct module resolution (e.g., look at webpack bundle analyzer)
  • verifiedRun the application in dev mode and navigate to a page that uses aliased imports; no module not found errors
  • verifiedRun unit tests with aliased imports; all pass
  • verifiedAdd a console.log in a file that imports via alias and confirm the module exports correctly
  • verifiedCheck the resolved path using Node's require.resolve or import.meta.resolve (ESM)
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningNot restarting your TypeScript server (VS Code) after changing tsconfig paths
  • warningForgetting to update all build tools (Webpack, Jest, etc.) when changing tsconfig paths
  • warningUsing relative paths in paths mapping like './src/*' when baseUrl is '.' - leads to double relative
  • warningAssuming that tsc --noEmit success means runtime will work (it doesn't check runtime resolution)
  • warningCopying tsconfig paths from another project without adjusting baseUrl and src directory
  • warningIgnoring case sensitivity in file names on Linux/macOS
( 07 )War story

The Missing Slash: A Path Alias Nightmare

Senior Frontend EngineerNext.js 13, TypeScript 5, Jest, React Testing Library

Timeline

  1. 09:15Developer A merges a PR that adds path aliases to tsconfig.json: '@/*': ['./src/*'] with baseUrl: '.'
  2. 09:30Developer B pulls latest, runs `npm run dev` - app crashes with 'Cannot find module @/components/Header' in browser console
  3. 09:35Developer B checks VS Code - no red squiggles; TypeScript compiles fine
  4. 09:40Developer B checks Next.js config - no resolve.alias defined; assumes Next.js handles it automatically
  5. 09:50Developer B adds resolve.alias in next.config.js: '@': path.join(__dirname, 'src') - still broken
  6. 10:00Developer B checks the dist folder - sees that alias paths are not transformed; Next.js side is not resolving
  7. 10:10Developer B digs into Next.js docs: Next.js supports tsconfig paths only if baseUrl is '.' and paths use 'src/*' not './src/*'
  8. 10:15Developer B changes tsconfig paths to '@/*': ['src/*'] - still broken; clears .next cache
  9. 10:20After clearing cache, app works. Also discovered that Jest tests were failing with same error - fixed with moduleNameMapper
  10. 10:30Developer A adds Jest moduleNameMapper in a follow-up PR to prevent test failures

I was the unlucky one who pulled the latest main branch only to find the dev server spewing 'Cannot find module @/components/Header' errors. The app wouldn't even render. My first instinct: check tsconfig.json. It had the paths set up: '@/*': ['./src/*'] with baseUrl: '.'. Looked fine. VS Code was happy, TypeScript compiled without complaint. So why was Next.js choking?

I spent 20 minutes adding resolve.alias to next.config.js, restarting the server, even deleting .next folder. Nothing. Then I actually read the Next.js documentation (I know, radical). Turns out Next.js has built-in support for tsconfig paths, but it expects a very specific format: baseUrl must be '.' and paths must not have a leading './'. So I changed './src/*' to 'src/*'. Still broken. Then I cleared .next cache again (hard refresh) - and it worked.

The cherry on top: our Jest tests were also failing with the same alias errors. I added moduleNameMapper to jest.config.js: '^@/(.*)$': '<rootDir>/src/$1'. Created a follow-up PR to fix that permanently. The lesson: path aliases are a three-headed monster - tsconfig, bundler, and test runner all need to agree. And never trust the IDE squiggles.

Root cause

tsconfig paths had a leading './' which Next.js does not support; also missing Jest moduleNameMapper

The fix

Changed tsconfig paths to '@/*': ['src/*'] and added moduleNameMapper in Jest config. Also cleared build cache.

The lesson

Always check the bundler's specific requirements for tsconfig path support. When in doubt, read the docs and clear caches.

( 08 )Understanding TypeScript Path Resolution

TypeScript's path resolution works through the `baseUrl` and `paths` properties in tsconfig.json. `baseUrl` sets the base directory for resolving non-relative module names. `paths` defines a mapping from a pattern (e.g., `@/*`) to an array of fallback patterns (e.g., `['src/*']`). The compiler tries each pattern in order until it finds a match. For example, with baseUrl: '.' and paths: '@/*': ['src/*'], an import `@/utils/helper` resolves to `./src/utils/helper.ts` (relative to baseUrl).

Critical nuance: the paths patterns are relative to baseUrl. So if baseUrl is './src', then '@/*': ['utils/*'] would map to './src/utils/*'. Also, the `*` is a wildcard that matches any substring. The mapping is not a simple string replace; it's a pattern match. These paths only affect type-checking and IDE features. They do not affect module resolution at runtime by Node.js or bundlers.

( 09 )Bundler-Specific Configurations

Webpack uses `resolve.alias` to map module requests. For example: `resolve: { alias: { '@': path.resolve(__dirname, 'src') } }`. This tells webpack to replace `@/components/Header` with `./src/components/Header`. Vite uses `resolve.alias` similarly. Next.js has built-in support for tsconfig paths but only if they follow a strict format: baseUrl must be '.' and paths must use relative patterns without './' prefix (e.g., `@/*`: [`src/*`]). Next.js also requires clearing the `.next` cache after changes.

For Node.js runtime (e.g., with ts-node or tsx), you need a loader that resolves aliases. The package `tsconfig-paths` provides a register script: `ts-node -r tsconfig-paths/register`. For ESM, you might need `tsx` which has built-in support. Alternatively, you can use Node's `--experimental-specifier-resolution=node` flag, but that only works for node_modules resolution, not custom paths.

( 10 )Testing Frameworks: Jest and Vitest

Jest uses `moduleNameMapper` to transform aliases. The typical config: `moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1' }`. The regex `^@/(.*)$` captures everything after `@/`, and `<rootDir>` is Jest's root directory (usually where package.json is). Vitest uses `resolve.alias` similar to Vite: `resolve: { alias: { '@': '/src' } }`. Both require the alias paths to be absolute or relative to the project root.

Common mistake: using relative paths in moduleNameMapper like `'./src/$1'` instead of `<rootDir>/src/$1`. This causes resolution failures because Jest resolves the replacement relative to the test file, not the project root. Also, if your tsconfig paths use a different baseUrl, ensure the mapping matches the actual directory structure.

( 11 )Debugging Step-by-Step

First, isolate whether the issue is compile-time or runtime. Run `npx tsc --noEmit`. If it passes, the problem is definitely in the bundler/runtime. Next, check the bundler config. For webpack, add a console log in the alias resolution: `alias: { '@': path.resolve(__dirname, 'src') }` and verify the path exists. For Node.js, use `node -e "console.log(require.resolve('@/utils'))"` to see the resolved path.

If the alias resolves to the wrong file, check for case sensitivity. On Linux, `./src/MyModule.ts` is different from `./src/mymodule.ts`. Also check for missing file extensions: TypeScript can import without `.ts` extension, but bundlers may need extensions configured. Finally, clear all caches: node_modules/.cache, dist, .next, and .tsbuildinfo. Many alias issues are simply stale caches.

( 12 )Advanced: ESM and Package.json Exports

With Node.js ESM, you can define path aliases using the `exports` field in package.json. For example: `"exports": { "./*": "./src/*.js" }`. This works with Node's native ESM resolver. However, TypeScript may not pick up these exports unless you set `moduleResolution: "node16"` or `"nodenext"`. This approach avoids needing a separate bundler alias but requires careful mapping.

Another advanced technique: use TypeScript's `paths` with `rootDirs` to create virtual directories. This is useful for monorepos where you want to share types across packages. But it's easy to misconfigure. The simplest approach is still to define aliases in both tsconfig and the bundler, and keep them in sync using a shared configuration file or a tool like `tsconfig-paths`.

Frequently asked questions

Why does VS Code show no error but the build fails?

VS Code uses TypeScript's language service, which reads tsconfig.json paths. The build tool (webpack, etc.) does not read tsconfig paths unless configured. So VS Code resolves the alias correctly, but the bundler doesn't know about it. You need to configure the bundler separately.

Do I need to configure both tsconfig paths and bundler aliases?

Yes, unless your bundler has built-in support for tsconfig paths. Next.js and some other frameworks do, but most (webpack, Vite, Jest) do not. The tsconfig paths are for type-checking and IDE; bundler aliases are for actual module resolution.

What is the correct format for tsconfig paths?

Set `baseUrl` to `.` (project root) or `./src` if your source is in src. Then `paths` should be like `"@/*": ["src/*"]` (no leading `./`). The `*` is a wildcard. For example, `@/components/Header` maps to `src/components/Header.ts` relative to baseUrl.

How do I fix alias errors in Jest tests?

Add `moduleNameMapper` in jest.config.js: `"^@/(.*)$": "<rootDir>/src/$1"`. The regex captures the part after `@/`, and `<rootDir>` is Jest's root directory. Make sure the replacement path is correct relative to your project structure.

Can I use path aliases with Node.js directly (without a bundler)?

Yes, but you need to use a loader like `tsconfig-paths` (for ts-node) or `tsx` (which has built-in support). For ESM, you can use the `exports` field in package.json to create aliases that Node understands. However, this is more limited and requires careful configuration.