When TypeScript Type Errors Aren't Caught During Build
Why I Encountered This Problem
While developing with TypeScript, VSCode showed type errors with red underlines. Thinking "I'll fix it later," I ran the build, and the build succeeded.
// VSCode shows error
const user: User = { name: 'John' }; // ❌ Property 'age' is missing
// But build succeeds
npm run build
✓ Build successful!
Worse, runtime errors occurred after deploying to production. The whole point of TypeScript is type safety, but if type checking isn't done during build, it's meaningless.
At first I thought "doesn't TypeScript automatically type-check?" But turns out build tools can skip type checking depending on configuration.
What Confused Me Initially
I assumed using TypeScript meant automatic type checking. But confusing parts:
- Why do errors show in VSCode but build succeeds?
- Why do Vite/Webpack ignore type errors?
- What's the difference between
tscand build tools?
Especially "Next.js claims TypeScript support, so why doesn't it type-check?"
The 'Aha!' Moment
The turning point was accepting "build tools skip type checking for speed."
How Build Tools Handle TypeScript
I understood this through the "translation vs proofreading" analogy:
- TypeScript Compiler (
tsc): Translation + proofreading. Finds grammar errors (type errors) and reports them. Slow. - Build Tools (Vite, esbuild, SWC): Translation only. Ignores grammar errors and just translates. Fast.
graph LR
A[TypeScript Code] --> B{Build Tool}
B --> C[tsc]
B --> D[Vite/esbuild]
C --> E[Type Check + Transform]
D --> F[Transform Only, No Type Check]
E --> G[Slow but Safe]
F --> H[Fast but Risky]
style E fill:#9f9,stroke:#333
style F fill:#f99,stroke:#333
Most modern build tools (Vite, esbuild, SWC) don't type-check, they only transform TypeScript to JavaScript. Reason: speed.
tsc: Type check + transform → Slow (tens of seconds)esbuild: Transform only → Fast (seconds)
Solutions
1. Add Type Check to Build Script (Recommended)
Run type check separately in package.json:
{
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"type-check": "tsc --noEmit"
}
}
Explanation:
tsc --noEmit: Type check only, don't generate files&&: Build proceeds only if type check succeeds
Now build fails if type errors exist:
npm run build
# On type error
error TS2741: Property 'age' is missing in type '{ name: string; }'
# Build stops!
2. tsconfig.json Strict Mode
Make type checking stricter:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
3. Next.js Type Check
Next.js doesn't type-check during build by default. To enable:
// next.config.js
module.exports = {
typescript: {
// ⚠️ Dangerous: ignore type errors and build
ignoreBuildErrors: false, // default is false but set explicitly
},
};
Or add to build script:
{
"scripts": {
"build": "tsc --noEmit && next build"
}
}
4. Vite Type Check Plugin
Vite doesn't type-check by default. Use plugin:
npm install -D vite-plugin-checker
// vite.config.ts
import { defineConfig } from 'vite';
import checker from 'vite-plugin-checker';
export default defineConfig({
plugins: [
checker({
typescript: true, // Enable type checking
}),
],
});
Now type errors show as overlay during development.
5. Type Check in CI/CD
GitHub Actions example:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm run type-check # Run type check
- run: npm run build
This catches type errors before merging PRs.
6. Pre-commit Hook
Type check before commit:
npm install -D husky lint-staged
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"
// package.json
{
"lint-staged": {
"*.{ts,tsx}": [
"tsc --noEmit",
"eslint --fix"
]
}
}
Now commits are blocked if type errors exist.
Practical Tips
1. Gradual Type Checking
When adding type checking to existing projects, do it gradually:
// tsconfig.json
{
"compilerOptions": {
"strict": false, // Start with false
"noImplicitAny": true, // Enable one by one
"strictNullChecks": false
}
}
Fix errors while increasing options.
2. Type Check Caching
tsc is slow, so use caching:
{
"compilerOptions": {
"incremental": true, // Incremental compilation
"tsBuildInfoFile": ".tsbuildinfo"
}
}
This only type-checks changed files.
3. Parallel Type Check
Run build and type check in parallel:
npm install -D concurrently
{
"compilerOptions": {
"strict": true
},
"scripts": {
"build": "concurrently \"tsc --noEmit\" \"vite build\""
}
}
But beware: build proceeds even with type errors.
4. VSCode Configuration
Make type errors clearer in VSCode:
// .vscode/settings.json
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
Mistakes I Made
1. Using ignoreBuildErrors: true
Set Next.js to ignore type errors, then got production errors.
// ❌ Never do this!
module.exports = {
typescript: {
ignoreBuildErrors: true, // Dangerous!
},
};
Lesson: Always fix type errors. Don't ignore them.
2. Overusing @ts-ignore
Used @ts-ignore to temporarily ignore type errors, which later became real bugs.
// ❌ Bad habit
// @ts-ignore
const user: User = { name: 'John' };
Lesson: @ts-ignore is a last resort. Define types properly.
3. Overusing any Type
Used any to avoid type errors.
// ❌ Giving up type safety
const data: any = fetchData();
Lesson: Use unknown instead of any, and use type guards.
// ✅ Correct way
const data: unknown = fetchData();
if (typeof data === 'object' && data !== null && 'name' in data) {
console.log(data.name);
}
4. Overusing skipLibCheck: true
Skipped library type checking and missed library version conflicts.
// ⚠️ Use carefully
{
"compilerOptions": {
"skipLibCheck": true // Skip node_modules type check
}
}
Lesson: Use skipLibCheck for build speed, but manage library versions well.
Debugging Tips
1. Find Type Error Locations
# Output all type errors
tsc --noEmit
# Check specific file only
tsc --noEmit src/components/Button.tsx
2. Check Type Inference
Hover over variables in VSCode to see inferred types.
const user = { name: 'John', age: 30 };
// Hover shows: const user: { name: string; age: number; }
3. Find Type Definitions
// Cmd/Ctrl + click to go to type definition
import { User } from './types';
One-Line Summary
TypeScript type errors aren't caught during build because build tools skip type checking. Add tsc --noEmit to your build script.