Speeding Up Large TypeScript Monorepos with Project References
Learn how TypeScript project references enable incremental builds, reduce compile times, and keep large monorepos responsive.
02 Aug 2025, 03:41 UTC

Problem: Slow Rebuilds in a Growing Monorepo
As a TypeScript monorepo expands, each change triggers a full program rebuild. The compiler re‑checks every file, even those unrelated to the edit, which lengthens local feedback loops and slows CI pipelines. Teams notice longer wait times after editing a single utility or component.
Thesis: Enable Project References for Incremental Builds
TypeScript project references let you split a monorepo into separate projects that can be built independently. By marking each project as composite and listing its dependencies in a references array, the compiler stores incremental state in .tsbuildinfo files and rebuilds only what changed plus its dependents.
How References Work
"composite": truetells TypeScript to treat the project as a buildable unit and to write build information.- The
referencesarray in a project’stsconfig.jsonpoints to othertsconfig.jsonfiles that should be considered upstream dependencies. - Running
tsc -b(build) walks the reference graph, performs an incremental build, and updates the.tsbuildinfofiles.
Worked Example: Three‑Package Monorepo
Consider a repository with the following layout:
/
packages/
api/
tsconfig.json
web/
tsconfig.json
shared/
tsconfig.json
tsconfig.base.json
Each package defines its own configuration, extending a shared base.
Step 1: Make Each Project Composite
In packages/shared/tsconfig.json:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist/shared",
"composite": true
}
}
Repeat the same "composite": true addition in packages/api/tsconfig.json and packages/web/tsconfig.json.
Step 2: Declare Dependencies via References
API depends on Shared:
{
"extends": "../../tsconfig.base.json",
"references": [{ "path": "../shared" }],
"compilerOptions": {
"outDir": "dist/api",
"composite": true
}
}
Web also depends on Shared (and could depend on API if needed):
{
"extends": "../../tsconfig.base.json",
"references": [{ "path": "../shared" }],
"compilerOptions": {
"outDir": "dist/web",
"composite": true
}
}
Step 3: Run the Build
From the repository root, execute:
tsc -b
This command reads the reference graph, builds Shared first, then API and Web. Subsequent runs after editing a file in packages/shared will rebuild Shared and then only those projects that reference it (API and Web). Editing a file isolated to packages/api triggers a rebuild of API alone.
Verification Steps
- After the first
tsc -b, look for.tsbuildinfofiles in eachdist/folder. Their presence indicates incremental state is stored. - Modify a file in
packages/shared(e.g., add a comment). Runtsc -bagain. The output should list only Shared, API, and Web as being processed, not any unrelated projects. - Run a clean build with
tsc -b --cleanto remove the.tsbuildinfofiles, then runtsc -bonce more to compare timings. The second incremental build should be noticeably faster than the clean build.
Trade‑Offs and Limitations
Project references introduce configuration overhead: every package needs a tsconfig.json with "composite": true and a correct references array. Forgetting the composite flag causes the compiler to fall back to a full program build, losing the incremental benefit.
Tooling must understand the reference model. Editors that rely on tsc -w or custom scripts may need to be updated to invoke tsc -b instead. Some linters and test runners that invoke tsc directly might bypass the incremental mechanism unless configured to use the build command.
Circular reference detection is limited. If a circular dependency is introduced, TypeScript will report an error only after a full build; incremental runs may silently succeed until the cycle forces a full program rebuild.
Actionable Closing
To adopt project references in your monorepo:
- Add
"composite": trueto each package’stsconfig.json. - Populate the
referencesarray with paths to upstreamtsconfig.jsonfiles. - Use
tsc -bfor local development and CI builds. - Verify incremental behavior by checking
.tsbuildinfofiles and observing scoped output after small edits. - Keep an eye on tooling compatibility; update scripts to call the build command rather than a raw
tscinvocation.
When configured correctly, you’ll see faster edit‑compile cycles and more responsive CI, letting the team focus on writing code rather than waiting for the compiler.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.