Managing Your Monorepo with Turborepo
1. Why Choose a Monorepo?
A Monorepo, or single code repository, is a strategy that stores multiple independent projects or packages in one repository. Compared with the Polyrepo model, where each project has its own repository, a Monorepo offers several notable advantages:
- Code sharing: Sharing components, utility functions, or type definitions between projects becomes very easy.
- Atomic commits: If a feature change touches multiple packages, it can be completed in a single commit, keeping versions consistent.
- Simplified dependency management: All projects share one
node_modulesdirectory, or optimize it through pnpm/yarn workspaces, reducing dependency conflicts and inconsistent versions.
As the repository grows, however, a Monorepo also brings a challenge: build performance.
2. The Pain Points of a Monorepo
Imagine that your repository contains docs, webapp, and a shared ui component library. When you only fix a typo in docs, the last thing you want is for every project in the repository, including webapp and ui, to be rebuilt and tested.
Traditional Monorepo management tools such as Lerna have limitations in task orchestration and build caching, resulting in:
- Repeated work: Every CI/CD run builds and tests everything from scratch, even when most of the code has not changed.
- Long build times: Tasks cannot make effective use of multiple CPU cores to run in parallel.
- Complex scripts: Complicated
package.jsonscripts are needed to control task execution order manually.
3. Turborepo: Built for Speed
Turborepo is a high-performance build system designed for JavaScript/TypeScript Monorepos and later acquired by Vercel. It solves the problems above through two core technologies: incremental builds and remote caching.
a. Incremental Builds and Task Pipelines
Turborepo lets you define dependencies between tasks in a root-level turbo.json file.
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}dependsOn: ["^build"]: The^symbol means that a package’sbuildtask depends on thebuildtasks of every package it depends on. Turborepo executes tasks in the most efficient parallel order based on this graph.outputs: Tells Turborepo which output files the task produces.
When you run pnpm turbo build, Turborepo calculates which files have changed and rebuilds only the affected packages. If a package’s source and dependencies have not changed, Turborepo uses the previous build output directly, which is instantaneous.
b. Remote Caching
This is Turborepo’s “killer feature.” It not only caches build results on your local machine but can also upload those caches to a shared remote server, such as Vercel or your own S3 bucket.
This means:
- Your colleague can pull the latest code and, if you have already built the part they need, download the cache instead of rebuilding it locally.
- The CI/CD server can connect to the same remote cache. Once a PR has been built and cached in CI, other developers and later CI tasks can benefit from it immediately.
This can reduce a whole team’s build time from tens of minutes to a few minutes or even tens of seconds.
Conclusion
Turborepo did not invent the Monorepo, but by introducing advanced caching and task scheduling, it greatly improves the Monorepo development experience and addresses its central performance bottleneck. If you use or plan to adopt a Monorepo architecture, Turborepo is undoubtedly a powerful tool that can save you and your team a great deal of time.