Bundler Ignores tsconfig "paths" Aliases - Fix Resolution in CI
A path alias like @/components works in the type-checker because tsconfig paths teaches tsc, but the bundler resolves modules independently and does not read tsconfig. So tsc is happy while webpack/Vite/Rollup report the alias as unresolvable.
What this error means
Type-checking passes, but the bundle step fails with Module not found: Can't resolve '@/...' (webpack) or Failed to resolve import "@/..." (Vite). The alias resolves in the editor and tsc but not in the build.
// tsconfig.json paths are set, tsc --noEmit passes, but:
ERROR in ./src/App.tsx
Module not found: Error: Can't resolve '@/components/Header' in '/app/src'Common causes
Bundler resolution is separate from tsconfig
tsconfig paths only configures TypeScript. webpack, Vite, Rollup, and esbuild each resolve modules on their own and do not honor paths unless told to.
Alias declared in only one place
The alias exists in tsconfig but not in the bundler config (or vice versa), so the two drift and the build cannot map the specifier.
How to fix it
Mirror the alias in the bundler config
Declare the same alias the bundler understands, or derive it from tsconfig with a plugin.
// webpack: tsconfig-paths-webpack-plugin
resolve: { plugins: [new (require('tsconfig-paths-webpack-plugin'))()] }
// vite: vite-tsconfig-paths
// import tsconfigPaths from 'vite-tsconfig-paths'; plugins: [tsconfigPaths()]Or hand-declare matching aliases
- webpack: add
resolve.aliasentries matching each tsconfigpathsmapping. - Vite/Rollup: add
resolve.aliasentries with the same prefixes. - Keep
baseUrl/pathsand the bundler alias in lockstep so they never diverge.
How to prevent it
- Use a plugin that derives bundler aliases from tsconfig
pathsso the two cannot drift. - Run the actual build in CI, not just
tsc --noEmit, so resolution gaps surface. - Document that tsconfig
pathsconfigures the type-checker only.