TypeScript "paths"/"baseUrl" Alias Not Resolving - Fix tsconfig in CI
A paths alias (@/*) that works in your editor can fail in CI when baseUrl is missing, when paths are defined in a base tsconfig that the project does not correctly extend, or when the bundler/runtime has no matching alias of its own.
What this error means
tsc reports TS2307: Cannot find module '@/...' in CI even though the editor resolves it, or the type-check passes but the runtime/bundler fails on the same alias. It is deterministic and tied to config inheritance.
src/app/page.tsx:2:20 - error TS2307: Cannot find module '@/lib/api' or
its corresponding type declarations.
2 import { api } from '@/lib/api'
~~~~~~~~~~~Diagnose it: which tsconfig and which compiler?
A TypeScript error that appears only in CI usually means the runner is compiling with a different config or a different compiler version than your editor. Your editor uses the workspace TypeScript and the nearest tsconfig.json; CI uses whatever the lockfile resolved and whatever config the build script names.
# what CI will actually use
npx tsc --version
npx tsc --showConfig | head -40
# which files are in the program (a missing include is a common cause)
npx tsc --listFiles | wc -l
# type-check only, no emit, same as most CI gates
npx tsc --noEmitCommon causes
baseUrl missing or paths not inherited
Older TypeScript requires baseUrl for paths to resolve. If paths live in an extended base config, the relative resolution can break unless the extending project re-declares them or uses the right relative roots.
Bundler/runtime lacks the matching alias
tsc paths only teach the type-checker. The bundler (Webpack resolve.alias, Vite resolve.alias) or Node runtime needs its own alias, or the alias resolves at type-check time but fails at build/run time.
How to fix it
Declare baseUrl and paths in the project tsconfig
Define both so resolution does not depend on inheritance quirks.
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
}
}Give the bundler the same alias
Mirror the tsconfig alias in the bundler (or derive it) so build/runtime resolution matches.
// vite.config.ts (or use vite-tsconfig-paths to derive from tsconfig)
resolve: { alias: { '@': '/src' } }Pin the compiler so unrelated updates cannot break the gate
TypeScript adds errors in minor releases. An unpinned compiler turns a routine dependency update into a red build on code nobody touched, which is the most common false alarm in a TypeScript CI pipeline.
// package.json
{
"devDependencies": {
"typescript": "5.6.3" // exact, not ^5.6.3
}
}How to prevent it
- Declare
baseUrl+pathsin the project tsconfig, not only a base config. - Keep bundler/runtime aliases in sync with tsconfig
paths(or derive them). - Run
tsc --noEmitand the real build in CI so editor-only resolution gaps fail.