How to Speed Up TypeScript Builds in Monorepos Using Project References

Surprising fact: teams that adopt TypeScript Project References and incremental builds commonly see warm build times drop by 4x–10x — and CI times fall by 30–70% when combined with caching. If your monorepo feels sluggish, this is the lever that pays back fastest.


Supporting image 2 — realistic IDE/terminal close-up (landscape, 4:3): a dark-themed terminal/VS Cod

Supporting image 3 — CI/CD pipeline visualization (landscape): a stylized horizontal flow showing mo

TL;DR — Quick wins to speed up TypeScript build

Enable composite + incremental in package tsconfigs, create a root tsconfig.json that lists references, and run builds with tsc --build. Add CI caching for .tsbuildinfo and emit directories. Use project references for type-check and declaration emit while using esbuild or swc for fast dev transpiles.

Why TypeScript build times explode in monorepos

Monorepos concentrate code, dependencies and type-surface area. Common pain points:

  • Every tsc run type-checks the whole repo if not partitioned.
  • Cascading recompiles: a small change in a core package forces rebuilds downstream.
  • CI cold builds recompile everything, increasing minutes billed (often tens of minutes).

Symptom: local watch latency > 2s for common edits, or PR build times ballooning to 10–30 minutes. In practice, teams often see 2–6x slower developer feedback loops compared to a referenced setup.

Understanding TypeScript Project References and composite projects

Project References let you break a monorepo into smaller TypeScript “projects.” Each project emits its own declaration files and a .tsbuildinfo. The TypeScript compiler then reasons about inter-project changes instead of retypechecking everything.

Key flags:

  • composite: true — required for referenced projects (enables declaration output and build info).
  • incremental: true — writes .tsbuildinfo to speed subsequent builds.
  • tsc --build (or tsc -b) — orchestrates builds across references and skips unchanged projects.

How to restructure a monorepo: practical tsconfig layout

Minimal layout (packages/foo, packages/bar):

{
  "files": [],
  "references": [
    { "path": "packages/foo" },
    { "path": "packages/bar" }
  ]
}

In packages/foo/tsconfig.json:

{
  "compilerOptions": {
    "composite": true,
    "incremental": true,
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src",
    "module": "esnext",
    "target": "es2020"
  },
  "include": ["src"]
}

Then run:

tsc --build --verbose
# For watch mode
tsc -b -w

Path mappings

If packages import each other via names, set paths in the root tsconfig or use your package manager's hoisting/symlink behavior. For pnpm, be mindful of symlink layout — add explicit path mappings if editors can't resolve types.

Integrating with bundlers and declaration files

Unique insight: use TypeScript project references for authoritative type-checking and declaration (.d.ts) generation, and use a fast bundler (esbuild/swc) for dev builds. This hybrid avoids double work — tsc does type/declaration work once, while esbuild performs near-instant transpiles.

  • Emit declarations from referenced projects (declaration: true).
  • Consume compiled outputs or declarationMap in bundlers where possible.
  • For Next.js or webpack, configure module resolution to prefer package types fields pointing at emitted declarations.

CI strategies: caching, parallel builds, orchestration

Cache these artifacts in CI:

  • .tsbuildinfo files (speed incremental compile)
  • dist/build outputs and declaration files

Recommended cache key pattern: ts-buildinfo-{{hash of lockfile}}-{{commit short}} but pair it with a secondary key that uses package.json hashes to allow safe reuse. Use remote cache (Nx Cloud, Turborepo remote cache) for large teams.

Measuring improvements & a real-world case study

Benchmark patterns to measure: cold build, warm incremental build, and watch latency. Example metrics to collect: wall time, CPU, and CI minutes billed.

Case study — FinTech startup (60 packages): After enabling project references, configuring incremental, and adding CI caching for .tsbuildinfo and dist artifacts, the team saw:

  • Cold CI build time drop: 12m → 5m (58% reduction)
  • Warm incremental build on PR: 90s → 18s (5x faster feedback)
  • Developer edit→test cycle reduced from ~45s to ~8s on average

These gains freed ~20% of CI concurrency and reduced billable minutes substantially.

Troubleshooting common pitfalls

  • Circular references: identify via tsc --build --verbose and refactor to remove cycles.
  • Stale .tsbuildinfo: delete when changing compilerOptions that affect emit.
  • pnpm symlink resolution oddities: add explicit paths or use preserveSymlinks settings in Node tooling.

Migration checklist (quick)

  1. Audit packages and dependency graph (tooling: dependency-cruiser).
  2. Add composite & incremental to each package tsconfig.
  3. Create root tsconfig.json with references.
  4. Run tsc -b, fix errors, add path mappings.
  5. Add CI cache steps for .tsbuildinfo and dist.
  6. Adopt hybrid strategy: tsc for types/declarations, esbuild for dev bundling.


Supporting image 1 — infographic-style diagram (square or portrait): a clean, labeled visualization

Conclusion — next steps

Project References are a pragmatic, high-ROI way to speed up TypeScript builds in monorepos. They reduce redundant work, enable parallelism, and pair well with CI caching and fast bundlers. In my experience, combining references with incremental build caching is the single most effective step teams take to reduce PR feedback time.

Get the complete guide with templates and checklists in our digital product — includes automated scripts to generate references, CI cache key recipes for GitHub Actions/CircleCI/GitLab, and troubleshooting playbooks. Get the complete guide with templates and checklists in our digital product.MonoRepo Speed Blueprint

Further reading: API design best practices, debugging techniques, microservices architecture

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