Cannot find module '@/components/Button' or its corresponding type declarations shows up the moment your editor or type checker understands a path alias but the tool actually running your code doesn't — a mismatch between what TypeScript's compiler sees and what your bundler, test runner, or Node.js resolves at runtime.
This error means something in your toolchain doesn't have the alias-to-path mapping that another part of your toolchain does — most commonly, tsconfig.json defines the alias for type checking and editor autocomplete, but the actual bundler or runtime executing the code has no matching configuration to resolve it.
Why This Error Happens
Path aliases like @/ are a TypeScript/editor-level convenience defined in tsconfig.json's paths option — TypeScript uses this purely for type checking and IDE navigation, it does not rewrite the alias into a real path when your code actually runs. Something else (your bundler, Node's module resolver, or your test runner) has to separately know how to resolve @/ to an actual file path, or it throws this error at runtime even though tsc reports no problem.
Reproducing and Diagnosing the Error
A typical setup where the mismatch appears:
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
// src/index.ts — type checks fine, but fails at runtime under plain ts-node or Jest
import { Button } from "@/components/Button";
Running this directly with ts-node or an unconfigured Jest setup throws Cannot find module '@/components/Button' because neither has the alias mapping — only tsc read tsconfig.json's paths.
Core Concepts Behind This Error
tsconfig.json paths only affect type checking, not runtime resolution — this is the single most important thing to internalize about this error; every runtime environment executing your code needs its own separate alias configuration.
Each tool needs matching alias config: Next.js and most modern bundlers (Vite, webpack via plugins) read tsconfig.json paths automatically for bundling, but Jest, ts-node, and plain Node.js do not — each needs explicit configuration (moduleNameMapper for Jest, tsconfig-paths for ts-node, or Node's native imports field in package.json).
Case sensitivity matters across operating systems — an alias resolving correctly on a case-insensitive filesystem (macOS default, Windows) can fail on a case-sensitive one (Linux, most CI runners) if the alias path's casing doesn't exactly match the actual file's casing.
Build output and source aliasing are separate concerns — an alias resolved correctly in source via a bundler can break after compilation to plain JavaScript if the compiled output doesn't have equivalent alias resolution (native Node.js doesn't understand paths at all), which is why libraries built for distribution generally avoid path aliases entirely.
Fixing "Cannot find module '@/...'"
Fix 1: Add matching alias configuration to every tool that executes your code, not just tsconfig.json. For Jest:
// jest.config.js
module.exports = {
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
};
For ts-node, register tsconfig-paths:
// package.json
{ "scripts": { "start": "node -r tsconfig-paths/register -r ts-node/register src/index.ts" } }
Fix 2: For Next.js and Vite projects, verify the bundler is actually reading tsconfig.json (usually automatic) rather than relying on a separate, out-of-sync alias config file — a common cause is a vite.config.ts resolve.alias block that's drifted out of sync with tsconfig.json's paths.
Fix 3: For compiled library output, avoid path aliases in code intended for distribution, using relative imports instead — since consumers of your published package won't have your tsconfig.json's alias configuration, aliases that work in your dev environment will break for them.
Why Does This Error Only Happen in Tests or Scripts, Not the Dev Server?
Because your dev server (Next.js, Vite) typically reads tsconfig.json paths automatically as part of its bundling process, while separate tools like Jest or standalone scripts run outside that bundling pipeline and need their own explicit alias configuration — the dev server "just working" is exactly why the mismatch is easy to miss until a different tool runs the same import.
Preventing This Error in Production
Configure path alias resolution explicitly for every tool in your pipeline that executes TypeScript or JavaScript containing aliased imports — dev server, test runner, and any standalone script runner — rather than assuming tsconfig.json alone is sufficient. Verify alias resolution in CI specifically, since case sensitivity differences between local development and CI runners are a common source of aliases that only fail in the pipeline.
If you hit this error, check which specific tool is throwing it first — the fix is almost always adding that tool's own alias mapping, not changing tsconfig.json, which is likely already correct.