Monorepo vs Polyrepo
The monorepo vs polyrepo debate is one of the most relitigated arguments in frontend engineering. The honest answer is: both are valid, and the right choice depends almost entirely on your team structure and how much code you actually share.
What is a polyrepo?
A polyrepo is the default: each project, app, or service lives in its own repository. This is how most teams start and how most open-source projects are structured.
Each repo has its own CI pipeline, its own dependency versions, and its own release cadence. Teams are fully autonomous — they never need to coordinate with another team to merge a change.
What is a monorepo?
A monorepo is a single repository containing multiple projects. The projects can be completely independent (different apps, different deploy targets) or tightly related (a library and the apps that consume it).
Monorepos are used by Google, Meta, Microsoft, and Airbnb at scale. Tools like Nx, Turborepo, Bazel, and Pants exist specifically to make large monorepos fast and maintainable.
Side-by-side comparison
| Concern | Monorepo | Polyrepo |
|---|---|---|
| Code sharing | Trivial — just import | Requires publishing to a registry |
| Atomic changes | One PR across all projects | Requires coordinating multiple PRs |
| CI speed | Slower without smart tools | Fast per-repo, slow overall |
| Dependency alignment | Enforced — one version | Diverges naturally over time |
| Team autonomy | Lower (shared tooling rules) | Higher (each team chooses) |
| Onboarding | Harder (everything is visible) | Easier per repo, harder overall |
| Tooling overhead | High (Nx, Turborepo, Bazel) | Low — standard npm scripts |
| Breaking changes | Caught immediately (build fails) | Hidden until consumers upgrade |
When monorepos win
You share a lot of code. A design system, a set of API clients, a component library — if these are used by three or more apps, the overhead of publishing and versioning them is significant. A monorepo eliminates this.
You make atomic changes across packages. Renaming an API endpoint that is called in four apps? One PR in a monorepo. Four coordinated PRs in a polyrepo, each needing to be merged in the right order.
You want enforced consistency. One TypeScript config, one ESLint config, one version of React. In a polyrepo, these drift.
Your teams collaborate closely on shared infrastructure. Platform teams maintaining a design system, an auth library, or a shared analytics package benefit enormously from monorepo visibility.
When polyrepos win
You have completely independent products. If App A and App B share no code and are owned by separate business units, forcing them into a monorepo adds overhead with no benefit.
Your teams need maximum autonomy. Polyrepos let teams choose their own tooling, upgrade dependencies on their own schedule, and operate entirely independently. This matters when teams have different technology stacks or very different release cadences.
Security or compliance requires separation. Some organisations cannot have all source code visible to all engineers. Polyrepos enforce this at the repository access control level.
You have a small team. A single team of 3 engineers does not need Nx.
A packages/ directory in a single repo is enough if they need code sharing.
Nx vs Turborepo
These are the two dominant monorepo tools for frontend teams today. They solve the same core problem — making large repos fast — but with very different philosophies.
Nx
Nx is a full monorepo framework. It does not just run tasks; it understands your project graph, enforces boundaries, and provides generators to scaffold apps and libraries consistently.
Key capabilities:
- Affected commands — only rebuild and retest what actually changed, based on the dependency graph.
- Computation caching — never run the same build twice locally or in CI.
- Project graph — visualise every dependency across all apps and libs (
nx graph). - Generators —
nx g @nx/react:app my-appscaffolds with your team's conventions. - Module Federation support — first-class
@nx/react:module-federationgenerators for microfrontends. - Workspace constraints — enforce that
checkoutcannot import frommarketingvia lint rules. - Remote caching — share build cache across all CI machines via Nx Cloud.
Nx is opinionated. It introduces project.json, its own plugin system, and
a concept of "targets". The learning curve is real, but the payoff at scale is
significant.
Best for: large teams, microfrontend architectures, enterprise monorepos, projects where code generation and enforced constraints matter.
Turborepo
Turborepo is a task runner for monorepos. It does one thing — run tasks in the right order, cache the results, and parallelise everything it can. It adds almost no structure to your repo.
Key capabilities:
- Pipeline definition — define task dependencies in
turbo.json(builddepends on^buildupstream). - Computation caching — same idea as Nx: skip tasks whose inputs haven't changed.
- Parallel execution — tasks with no dependency on each other run concurrently.
- Remote caching — share cache via Vercel Remote Cache (free on Vercel, self-hostable).
- Zero lock-in — your
package.jsonscripts stay exactly as they are.
Turborepo does not generate code, does not enforce boundaries, and does not
care about your project graph beyond what you declare in turbo.json. This
makes it much simpler to adopt — you can add it to an existing repo in an
afternoon.
Best for: Next.js / Vercel stacks, small-to-medium teams, repos that just
need faster CI without restructuring, teams that prefer standard package.json
conventions.
Side-by-side
| Nx | Turborepo | |
|---|---|---|
| Philosophy | Full framework | Task runner only |
| Setup complexity | High | Low |
| Code generators | Yes (nx g) | No |
| Module Federation | First-class support | DIY |
| Project graph | Full dependency graph | Declared in turbo.json |
| Boundary enforcement | Yes (lint rules) | No |
| Remote caching | Nx Cloud | Vercel Remote Cache |
| Vercel integration | Works, not native | Native |
| Lock-in | Medium (Nx conventions) | Very low |
| Best stack | Any, strong on React/Angular | Next.js / Vercel |
Which one?
If you are building microfrontends with Module Federation: use Nx. The generators and project graph save weeks of manual wiring.
If you are building a Next.js monorepo on Vercel with shared packages: use Turborepo. It integrates natively with Vercel and adds almost no overhead.
If you are unsure: start with Turborepo. It is additive — you are not restructuring your repo, just speeding it up. You can always migrate to Nx later if you need generators and constraints.
Without one of these tools, a monorepo becomes a liability at scale: builds take forever because everything rebuilds on every CI run.
Decision tool
The hybrid approach
Many companies end up with a hybrid: a monorepo per domain, not a single global monorepo. The platform team's design system lives in one monorepo, the checkout domain in another, the marketing site in another. Each monorepo is fully self-contained, but inter-domain dependencies are published to an internal registry.
This is often the most pragmatic middle ground.
What Google actually does
Google runs a single monorepo with billions of lines of code — but they built Blaze (now open-sourced as Bazel) to make it work. The tooling investment is enormous. The lesson is not "use a monorepo" but "if you use a monorepo, invest in the tooling".