Typecheck in CI
Running tsc --noEmit in CI catches type errors that ESLint alone misses and blocks merges before broken builds reach production.
Recipe
Quick-reference recipe card - copy-paste ready.
{
"scripts": {
"typecheck": "tsc --noEmit -p tsconfig.json"
}
}- run: npm ci
- run: npm run typecheckWhen to reach for this:
- Every TypeScript Node service or library.
- You skip emitting in CI (
noEmit) for faster feedback than fullbuild. - Monorepos need per-package or root solution typecheck.
Working Example
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src", "test"]
}// package.json
{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json"
}
}# .github/workflows/ci.yml
name: ci
on: [pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24.18.0"
cache: npm
- run: npm ci
- run: npm run typecheck
- run: npm run lint
- run: npm test
- run: npm run buildWhat this demonstrates:
typecheckruns before tests to fail in seconds on signature errors.strict: trueis non-negotiable for backend services.buildstill runs in CI to verify emit config anddist/output.
Deep Dive
How It Works
tsc --noEmittypechecks without writing files - ideal CI merge gate.- Separate
tsconfig.build.jsonemits onlysrc/for production bundles. skipLibCheck: truespeeds CI by skipping.d.tsvalidation innode_modules.- Monorepos:
turbo run typecheckwithdependsOn: ["^build"]when types come from compiled deps.
CI Ordering
| Step | Why this order |
|---|---|
npm ci | Deterministic graph |
typecheck | Fast signal on types |
lint | Logic and style |
test | Behavior |
build | Emit verification |
TypeScript Notes
{
"compilerOptions": {
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}- Stricter flags can be enabled org-wide once codebase is clean.
- Align
@types/nodewith Node 24.
Gotchas
- Only running build in CI - Missing
noEmitpath hides test-only type errors ifbuildexcludestest/. Fix: dedicatedtypecheckincluding tests. - Typecheck without building workspace deps - Apps fail on missing
.d.tsfrom packages. Fix:turbo run typecheck --filter=api...after^build. - Different tsconfig locally vs CI - Editor uses solution; CI uses wrong project. Fix: document
npm run typecheckas source of truth. skipLibCheckhiding bad overrides - Rare but painful. Fix: periodic local run withskipLibCheck: falseon upgrades.- Ignoring type errors in tests -
@ts-expect-errorwithout comments accumulates. Fix: lint ban excessive expect-error directives.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
tsgo / native preview | Experimenting on speed | Production CI needs stable TSC |
| Type-aware ESLint only | Supplement, not replace | You skip tsc entirely |
build as only gate | Tiny single-file scripts | Tests excluded from build config |
FAQs
Should typecheck include test files?
Yes, unless tests use a separate tsconfig.test.json referenced by a second CI step.
How fast is typecheck in monorepos?
Use project references and Turbo caching. Typecheck packages in dependency order once, cache hashes.
Does NestJS need special flags?
Nest uses tsconfig.build.json with emitDecoratorMetadata. Typecheck should use same experimentalDecorators settings as build.
Can I parallelize typecheck and lint?
Yes in separate CI jobs after npm ci. Both must pass before merge.
What about JavaScript checkJs?
allowJs + checkJs for gradual migration. Prefer full TS for new services.
Should CI cache tsc incremental?
--incremental with cached .tsbuildinfo helps large repos; Turbo often suffices.
How do path aliases affect CI?
tsconfig paths must match runtime (or use built package names). Misaligned aliases pass locally with tsx but fail tsc.
Is typecheck needed if build runs?
Yes when build include is narrower than typecheck. Tests and scripts stay covered.
How do we gate on strictness upgrades?
Enable one new flag per sprint with a tracking issue; CI enforces once zero errors.
Does express@5 need @types/express?
Express 5 ships types; remove duplicate @types/express if both are installed.
Related
- Linting Basics - ESLint complements tsc
- Linting Best Practices - full quality gate stack
- knip & Dead Code - unused export detection
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.