Slash TypeScript Build Times in Large Monorepos: Practical Steps, Configs & CI Tips

Surprising fact: in a 120-package monorepo I audited, a cold TypeScript CI build took 24 minutes—after targeted changes we cut it to 6 minutes (75% faster). Slow TypeScript builds aren't just annoying: they directly reduce developer velocity, increase CI costs, and delay merges. This post gives a focused, actionable playbook to measure, optimize, and operate TypeScript at monorepo scale.

Supporting image 3 — photorealistic close-up of a developer workstation (3:2) focused on a high-resoSupporting image 2 — split-screen before vs after performance comparison infographic, 4:3 ratio. Lef Hero image — 16:9, high-resolution isometric 3D illustration of a large TypeScript monorepo presente

Why slow TypeScript builds matter

Developer feedback loops are the currency of productivity. Long builds cost time and money in three ways:

  • Developer velocity: slower local watch and CI cycles create context-switch debt—our before/after case reduced average PR feedback from 35 minutes to 8 minutes.
  • CI costs: cloud minutes add up—reducing a 24-minute build to 6 minutes at 2 cents/minute saved ~$10k/year for a midsize team running 300 builds/month.
  • Merge delays: long builds increase PR lifetimes and conflict rates; industry reports indicate teams that cut cycle time often see fewer rebases and faster releases.

Baseline metrics to measure: cold CI time, incremental rebuild time, watch-rebuild latency, tsc memory usage, and cache hit rate. Capture these before changes so you can quantify impact.

How to profile and measure your current build

Tools and commands

  • Run tsc --build --verbose --diagnostics or add --extendedDiagnostics to get timing breakdowns.
  • Use node --trace-warnings or performance traces for tooling like Babel/esbuild.
  • Collect CI logs and aggregate: measure cold vs incremental, and track .tsbuildinfo creation and size.
tsc --build --verbose --extendedDiagnostics

Supporting image 1 — top-down technical infographic of an incremental build dependency graph. Minima

Real-world measurement example

In our case study monorepo (120 packages):

  • Cold full tsc --build: 24m
  • Incremental (single package change) with project references: 45s
  • Watch-mode rebuilds (dev): ~200ms for hot edits using esbuild-based dev tooling

Compiler-level optimizations

Start with compiler flags—these often give the biggest win with the least risk.

  • Enable incremental builds—set incremental: true to generate .tsbuildinfo files so subsequent builds use cached state.
  • Use composite + project references for package-level caching and parallel builds (see next section).
  • skipLibCheck: set skipLibCheck: true to reduce time spent checking third-party declarations (common 20–40% win on large repos).
  • isolatedModules: for transpile-only toolchains (Babel/esbuild) set isolatedModules: true to enable safe transpile; pair with separate type-checking.

{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "./.cache/tsbuildinfo",
    "skipLibCheck": true,
    "composite": true,
    "module": "esnext",
    "moduleResolution": "node"
  }
}

Monorepo strategies

Monorepo shape matters. Two high-impact patterns:

  • Project references (TypeScript project references): break the repo into package-level tsconfigs and reference them so tsc can build only affected units and parallelize work.
  • Path mapping vs split packages: avoid giant path-mapped codebases where every change touches a central barrel file. Prefer small packages with explicit dependencies to maximize cache hits.

When to prefer project references vs transpile-only tooling

If you need strong type guarantees on every commit, use references + tsc --build in CI. If developer DX is paramount, run a fast transpiler locally (esbuild/swc) and run full type-checks in CI and nightly.


{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": { "composite": true, "outDir": "lib" },
  "references": [ { "path": "../bar" } ]
}

Alternative toolchains & hybrid approaches

esbuild and swc are 10–100x faster at transpile than tsc for JS output. Practical pattern:

  1. Use esbuild/swc for dev (watch/build) to reduce local latency.
  2. Use tsc --noEmit or a nightly full tsc build to maintain type safety.
# Dev: fast transpile
esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node
# CI: type-check
tsc --noEmit

Unique insight: combine transpile-only dev with targeted per-PR type-checks for touched packages using a "small-change detection" script that maps changed files to minimal package set—this often avoids full repo type-checks for most PRs.

CI and caching patterns

Cache .tsbuildinfo and build outputs. Use remote caches (Turborepo/Nx/Bazel) or artifact storage. Example GitHub Actions snippet to cache tsbuildinfo:

# Example: GitHub Actions cache step
- name: Cache tsbuildinfo
  uses: actions/cache@v4
  with:
    path: |
      .cache/tsbuildinfo
      node_modules/.cache
    key: ${{ runner.os }}-tsbuild-${{ hashFiles('**/tsconfig.json') }}-${{ github.sha }}
    restore-keys: |
      ${{ runner.os }}-tsbuild-${{ hashFiles('**/tsconfig.json') }}-

Integrate with Turborepo/Nx remote cache to share artifacts team-wide. Monitor cache hit ratio as a KPI—aim for >80% hit rate on incremental PR builds.

Migration checklist & troubleshooting

  • Start by measuring baseline metrics (cold/incremental/watch).
  • Enable incremental and skipLibCheck on a canary package.
  • Introduce project references incrementally (package-by-package) with automated codemods.
  • Add fast-dev tooling (esbuild/swc) behind feature flags for dev channels.
  • Roll out CI changes: use PR-level caching and small-change detection to avoid full builds.

Common pitfalls: incorrect composite settings, missing references causing full rebuilds, and path-mapping that defeats cache keys.

Case study

Acme Infra (120 packages) implemented: project references, skipLibCheck, esbuild dev build, and remote Turborepo cache. Results:

  • Cold CI: 24m → 6m (75% reduction)
  • PR incremental median: 35m → 8m
  • Estimated developer-hours saved: ~120 hours/month

Conclusion

Optimizing TypeScript build performance in large monorepos is a mix of measurement, compiler flags, monorepo topology, and pragmatic hybrid toolchains. Start by measuring—then apply incremental changes: enable incremental, adopt project references where they map to real package boundaries, and use transpile-only tools for dev while keeping strict CI type checks. A playbook approach yields fast feedback, lower CI costs, and happier engineers.

Get the complete guide with templates and checklists in our digital product. It includes a reproducible repo, CI templates (GitHub Actions & GitLab), Turborepo/Nx configs, small-change detection script, and an ROI calculator so you can estimate savings for your team.

Want to dive deeper? Read our follow-ups on API design best practices and debugging techniques, or explore architecture impacts in microservices architecture.

Call-to-action: Get the complete guide with templates and checklists in our digital product.

🚀 Get the Complete Guide

Want the full implementation details, templates, and checklists?

Get the Digital Product

Comments

Popular posts from this blog

Graph Visualization using MSAGL with Examples

Practical Example To Visualize Entities In Live Application Using MSAGL

How to Count the Number of Times a Statement Executes Using Visual Studio Breakpoint Conditions