Table of contents
Keeping your organization's code well-organized and accessible across teams is always a good idea, but as codebases grow, it's important to choose the right repo structure to make that process as simple as possible. A monorepo that uses Turborepo strikes a balance between a more centralized or decentralized approach, offering flexibility while avoiding redundancy and over-complexity.
On one end of the spectrum, you have monolith repositories, which collect all of an app's components and dependencies in one place. At the other end are polyrepos, in which every app, package, and component has its own repo.
Both have their merits, but at Quantum Mob, we've found that monorepos (also called modular repos) are a happy medium between them, allowing engineers to focus on what's important - writing useful code - and avoid the pitfalls that accompany large and diverse codebases.
Advantages of Monorepos
In a monorepo, multiple apps can share the same repo, and while components and services are split into their own packages, these packages are shared between the apps according to individual dependency trees, rather than being independently maintained.
Think of a component library with 100+ frontend components, utils, and types that are used across multiple apps or teams. Replicating so many pieces over and over for a series of monolith repos is a tedious process prone to error, but in a monorepo you only need one of each, to be shared as needed between apps.
Consider also a backend-frontend bundle, typically a two-app bundle of React and Express.js. These have different dependencies, but share request and response types, as well as certain utils like date, formats, and data transform functions. As deeply interconnected but independent apps, it makes perfect sense for them to exist together in a monorepo instead of separately.
This type of advantage also applies to microservices, serverless functions, and cross-platform apps. Pieces shared between individual packages can be maintained more easily, and there's no need to worry about whether one app uses components A, B, and C, or B, C, and D — a monorepo makes it easy to include and maintain all these parts without complicating any particular deployment.
There are more general benefits as well. Monorepos make it easy to avoid code duplication or repeat tooling, and having a single source of truth is great for testing and seeing the immediate effect of any changes.
It also encourages standardization that might be lacking when there are tons of repos spread across teams — and it prevents siloing of teams and apps focused on their own dependencies and components. It's even faster to build — splitting an app into shared parts allows for improved parallelization, and you can just cache the ones that didn't change. Why rebuild them?
Why Turborepo?
We've found Turborepo is a good match for our needs. There are a few different categories within monorepo tooling: on the more fundamental side you have things like Yarn and NPM Workspaces, which establish the folder structure and basic sharing and dependency relationships. On the other end, you have package publishing tools like Lerna, NX and Rush that handle version and platform management for things like component and open source libraries.
Turborepo is in the task running and build tooling category, where the essential action takes place that makes monorepos work: analyzing, building, and importing components quickly and correctly, as well as transpiling if necessary. This is the really crucial bit, since even if you have the basic structure, you can't run it without the proper tooling to build and run tasks between the packages — and if you can't do that, then there's no point publishing the packages, since they won't work!
We found Turborepo to be simple to use with minimal configuration. It does one thing well: run tasks according to their dependency structure, handling caching, serial and parallel execution as needed. It can also be swapped out for NX if further capabilities are required.
Using a monorepo for us means less repetition, more consistency, and fewer errors from maintaining and synchronizing multiple repos. Instead, we spend more time writing useful code, collaborating across teams, and deploying standardized, modular, and clean codebases.