Hunchbite
ServicesGuidesCase StudiesAboutContact
Start a project
Hunchbite

Software development studio focused on craft, speed, and outcomes that matter. Production-grade software shipped in under two weeks.

+91 90358 61690hello@hunchbite.com
Services
All ServicesSolutionsIndustriesTechnologyOur ProcessFree Audit
Company
AboutCase StudiesWhat We're BuildingGuidesToolsPartnersGlossaryFAQ
Popular Guides
Cost to Build a Web AppShopify vs CustomCost of Bad Software
Start a Project
Get StartedBook a CallContactVelocity Program
Social
GitHubLinkedInTwitter

Hunchbite Technologies Private Limited

CIN: U62012KA2024PTC192589

Registered Office: HD-258, Site No. 26, Prestige Cube, WeWork, Laskar Hosur Road, Adugodi, Bangalore South, Karnataka, 560030, India

Incorporated: August 30, 2024

© 2026 Hunchbite Technologies Pvt. Ltd. All rights reserved.· Site updated February 2026

Privacy PolicyTerms of Service
Home/Guides/TypeScript Performance Optimization: Faster Builds, Better DX
Guide

TypeScript Performance Optimization: Faster Builds, Better DX

How to configure TypeScript for fast builds and responsive IDE feedback — incremental compilation, tsconfig tuning, decoupling type checking from transpilation, and project references for monorepos.

By HunchbiteMarch 30, 20269 min read
TypeScriptdeveloper experienceDX

TypeScript's type safety is worth the tradeoff — until the build takes 3 minutes and the IDE takes 5 seconds to show an autocomplete suggestion.

The performance problems are predictable and fixable. This guide covers the specific configurations that make TypeScript fast at scale: incremental compilation, tsconfig tuning, separating type checking from transpilation, and project references for monorepos.

Why TypeScript gets slow

Three things cause TypeScript to slow down as codebases grow:

1. No incremental builds. By default, tsc re-analyses every file from scratch. At 500+ files, this adds up.

2. Checking node_modules type definitions. Your dependencies ship .d.ts files. Checking them adds significant time, and you didn't write them — there's nothing actionable you can do with those errors.

3. Type checking coupled to the build. When your bundler runs tsc as part of the build, every hot-reload waits for type checking to complete. These are different concerns and should run separately.

tsconfig.json: the high-leverage settings

{
  "compilerOptions": {
    // Performance
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo",
    "skipLibCheck": true,
 
    // Correctness
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
 
    // Compatibility
    "moduleResolution": "bundler",
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "esnext"],
    "esModuleInterop": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

incremental: true — TypeScript saves a .tsbuildinfo file after each compilation. On subsequent builds, it reads this file and only rechecks files that have changed. First build is unchanged; subsequent builds are dramatically faster.

skipLibCheck: true — Skips type checking of .d.ts files in node_modules. This is almost always correct: you're not fixing library type errors, and checking them adds significant time for zero benefit.

noUncheckedIndexedAccess: true — Goes beyond strict mode. Array access (arr[0]) and object index access return T | undefined instead of T. This catches a class of runtime errors that strict misses.

Decouple type checking from your build

This is the highest-leverage change for development speed. Most setups run type checking as part of the build, which means every file save blocks on the TypeScript compiler.

The correct approach:

Your bundler handles transpilation only. Tools like SWC and esbuild strip TypeScript types to produce JavaScript near-instantly — they don't check types at all. This is correct for development: you need runnable code, not verified code.

Type checking runs separately. Run tsc --noEmit --watch in a terminal alongside your dev server. Errors appear in that terminal. They don't block your build.

In a Next.js project:

# Terminal 1: dev server (fast, no type checking)
pnpm dev
 
# Terminal 2: type checker (runs in background, reports errors)
pnpm tsc --noEmit --watch

Or as a package.json script:

{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch"
  }
}

In CI, run type-check as a separate job — in parallel with lint and tests, not blocking the build.

VS Code settings that matter for large codebases

Add .vscode/settings.json to your project so these apply to everyone on the team:

{
  "typescript.preferences.includePackageJsonAutoImports": "off",
  "typescript.suggest.autoImports": true,
  "typescript.updateImportsOnFileMove.enabled": "always",
  "editor.codeActionsOnSave": {
    "source.organizeImports": "explicit"
  }
}

includePackageJsonAutoImports: "off" — Without this, VS Code scans every package.json in node_modules when generating auto-import suggestions. In a monorepo, this causes significant autocomplete lag. Turning it off has no practical downside — it just stops suggesting obscure internal package paths.

Project references in monorepos

In a monorepo with multiple TypeScript packages, changing one package triggers a full recheck of all packages by default. Project references fix this.

Each package gets a tsconfig.json that declares its dependencies:

// packages/ui/tsconfig.json
{
  "extends": "@repo/typescript-config/base.json",
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "references": [
    { "path": "../utils" }
  ]
}

The root tsconfig.json references all packages:

// tsconfig.json (root)
{
  "files": [],
  "references": [
    { "path": "./packages/utils" },
    { "path": "./packages/ui" },
    { "path": "./apps/web" }
  ]
}

Build with tsc --build (or tsc -b) instead of tsc. TypeScript now understands the dependency graph and only rebuilds packages affected by your change.

composite: true is required on referenced packages — it tells TypeScript that this package will be referenced by others, and enables the incremental outputs that make this work.

Measuring the improvement

Before making changes, baseline your current build time:

time tsc --noEmit

After enabling incremental builds, run it twice — the second run shows the cached improvement:

time tsc --noEmit  # first run (cold)
time tsc --noEmit  # second run (should be 60-80% faster)

For a 500-file codebase, expect cold build around 15–30 seconds, incremental build 3–8 seconds. At 2,000 files, incremental builds stay fast while cold builds scale linearly.

The common mistakes

Running tsc in your webpack/vite config. ts-loader with transpileOnly: false runs full type checking on every file save. Switch to transpileOnly: true or replace with swc-loader entirely.

Not committing .tsbuildinfo. If CI deletes node_modules and .tsbuildinfo between runs, incremental builds don't help in CI. Cache .tsbuildinfo along with node_modules.

Circular dependencies. TypeScript handles circular imports differently from bundlers and they silently degrade type inference quality. Use madge --circular . to find them.


A slow TypeScript setup is usually a configuration problem, not a scale problem. These changes — incremental builds, skipLibCheck, decoupled type checking, and project references — are what we implement as part of every DX engagement.

If your team is spending meaningful time waiting on TypeScript, that's engineering time that should be building product.

→ Developer Experience Consultancy

Call +91 90358 61690 · Book a free call · Contact form

FAQ
Why does TypeScript slow down as a codebase grows?
TypeScript's type checker does more work as the codebase grows — more files to analyse, more types to infer, more cross-file relationships to track. Without incremental compilation, it re-checks everything from scratch on each build. The fix is a combination of incremental builds (cache what hasn't changed), skipLibCheck (skip node_modules type files), and decoupling type checking from transpilation so they don't block each other.
What's the difference between transpilation and type checking?
Transpilation strips TypeScript syntax to produce runnable JavaScript — tools like SWC and esbuild do this near-instantly because they don't validate types. Type checking verifies correctness against your type definitions — this is what `tsc` does and what's slow at scale. Separating them means your code runs immediately while type checking happens in the background, which is the right tradeoff for development speed.
Should I use project references in a monorepo?
Yes, if you have multiple packages with clear dependency relationships. Project references tell TypeScript exactly which packages depend on which, so a change in one package only triggers a recheck of that package and its dependents — not the entire monorepo. The overhead to set up is low; the build time savings at 20+ packages are significant.
Next step

Ready to move forward?

If this guide resonated with your situation, let's talk. We offer a free 30-minute discovery call — no pitch, just honest advice on your specific project.

Book a Free CallSend a Message
Continue Reading
guide

Drizzle ORM Setup Guide: Type-Safe Database Access with PostgreSQL

How to set up Drizzle ORM with PostgreSQL from scratch — schema definition, migrations, query patterns, connection pooling, and the configuration decisions that matter in production Next.js applications.

11 min read
guide

How Database Indexes Work (And Why the Wrong Index Is Worse Than None)

A technical guide to database indexes: B-tree internals, composite index column ordering, covering indexes, partial indexes, the write cost of over-indexing, EXPLAIN ANALYZE interpretation, and the common indexing mistakes that degrade production performance.

14 min read
All Guides