Fix TypeScript Path Alias "Cannot Find Module" Error

Topic: tsconfig-cannot-find-module-path-aliasUpdated 7/16/2026

Quick Answer

  • Root cause: TypeScript's tsc compiler does not convert paths aliases (e.g., @utils/*) to relative paths in compiled JavaScript output, causing Node.js to fail at runtime.
  • First checks: Verify baseUrl is set in tsconfig.json, confirm path alias keys match import statements exactly, and determine whether you're running TypeScript directly (ts-node) or compiled JavaScript.
  • Minimal fix for development: Install tsconfig-paths and run with node -r tsconfig-paths/register dist/index.js.
  • Minimal fix for production: Install tsc-alias and add it to your build script: "build": "tsc && tsc-alias".
  • Applicable environments: Any TypeScript project using tsconfig.json paths with ts-node, tsc compilation, or Node.js runtime. Not needed for projects using Webpack, Vite, or other bundlers that handle aliases natively.

What Problem It Solves

TypeScript's paths configuration in tsconfig.json lets you define import aliases like:

JSON
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@utils/*": ["src/utils/*"],
      "@components/*": ["src/components/*"]
    }
  }
}

This allows clean imports like import { helper } from '@utils/helper'. However, the TypeScript compiler (tsc) only uses paths for type checking—it does not rewrite these aliases to relative paths in the compiled JavaScript output. When Node.js runs the compiled files, it cannot resolve @utils/helper and throws Cannot find module.

Root Cause Analysis

The disconnect happens because:

  1. TypeScript compiler (tsc) treats paths as a type-resolution feature only
  2. Node.js runtime has no knowledge of tsconfig.json paths
  3. Compiled JavaScript retains the original alias import statements unchanged

This affects two main scenarios:

  • Development: Running TypeScript directly with ts-node (which can resolve paths)
  • Production: Running compiled JavaScript with node (which cannot)

Common Errors and Fixes

Error: Cannot find module '@utils/helper' or its corresponding type declarations

Solution: Verify tsconfig.json configuration:

JSON
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@utils/*": ["./src/utils/*"],
      "@components/*": ["./src/components/*"]
    }
  }
}

Ensure baseUrl is set and path patterns match. Then install tsconfig-paths and register it:

BASH
npm install --save-dev tsconfig-paths
node -r tsconfig-paths/register dist/index.js

Error: tsc compiles successfully, but Node.js runtime throws 'Cannot find module'

Solution: Use tsc-alias to rewrite aliases after compilation:

BASH
npm install --save-dev tsc-alias

Add to your package.json build script:

JSON
{
  "scripts": {
    "build": "tsc && tsc-alias",
    "start": "node dist/index.js"
  }
}

Error: tsconfig-paths/register does not work with ESM

Solution: For projects with "type": "module" in package.json, use the ESM loader:

BASH
node --loader tsconfig-paths/register dist/index.js

Or better, switch to tsx which natively supports both ESM and path aliases:

BASH
npm install --save-dev tsx
tsx dist/index.js

Error: Path alias works in development but fails in production

Solution: This is the classic development-production mismatch. Use tsc-alias in your production build pipeline:

JSON
{
  "scripts": {
    "build": "tsc && tsc-alias",
    "start:dev": "ts-node src/index.ts",
    "start:prod": "node dist/index.js"
  }
}

Comparison: tsconfig-paths vs tsc-alias vs tsx

SolutionTypeBest ForESM SupportProduction Ready
tsconfig-pathsRuntimeDevelopment, quick iterationLimited (needs loader flag)No (adds startup overhead)
tsc-aliasBuild-timeProduction buildsYesYes (no runtime dependency)
tsxRuntimeBoth dev and prodYes (native)Yes (but slight overhead)

Recommendation: Use tsconfig-paths with ts-node during development, and tsc-alias in your production build pipeline. For simpler projects, consider tsx as a unified runtime that handles both development and production.

Production Notes and Security Checks

  • Build pipeline: Always include tsc-alias as a build step in CI/CD. Missing this step will cause production failures.
  • Dependencies: Install tsconfig-paths or tsc-alias as devDependencies only—they are not needed at runtime if you use build-time alias replacement.
  • Explicit config: Use the --project flag to specify your tsconfig.json path explicitly to avoid accidental use of global configurations.
  • Path safety: Avoid absolute paths in paths configuration (e.g., /usr/lib/*) to prevent potential path traversal issues.
  • Dynamic imports: Path alias resolution does not work with dynamic import() expressions or require() calls using variables. Aliases must be statically analyzable strings.

FAQ

Q: Why does tsc compile successfully but Node.js still throws 'Cannot find module'?

A: TypeScript's compiler (tsc) only uses paths for type checking and does not rewrite aliases to relative paths in the compiled JavaScript output. The compiled files retain the original alias syntax (e.g., @utils/helper), which Node.js cannot resolve. You need a separate tool like tsc-alias (build-time) or tsconfig-paths (runtime) to handle the alias resolution.

Q: tsconfig-paths vs tsc-alias—which should I use?

A: Use tsconfig-paths during development with ts-node for fast iteration—it registers a hook that resolves aliases at runtime. Use tsc-alias for production builds—it rewrites aliases to relative paths in the compiled output, eliminating runtime dependencies. For a unified approach, consider tsx which handles both development and production with native alias support.

Q: My project uses ESM ("type": "module"). How do I make path aliases work?

A: For ESM projects, tsconfig-paths requires the --loader flag: node --loader tsconfig-paths/register dist/index.js. A cleaner solution is to use tsx (tsx dist/index.js), which natively supports ESM and path aliases. Alternatively, use tsc-alias in your build step, which works with both CommonJS and ESM output.

Related Guides