Skip to content
Guide

Monorepo

A monorepo puts many projects in one repository. Google, Meta, and most modern platform teams run this way, and for good reasons. The benefits are real, and so is the tax: CI has to scale with the size of the change rather than the size of the repo, and merges have to be coordinated so one busy main branch does not become a bottleneck. This guide covers both halves.

By Julien Danjou, Co-founder & CEO of MergifyUpdated

In one paragraph

A monorepo is one repository holding many projects that are built, tested, and released together. It is a decision about where code lives, separate from how you deploy it. You get atomic cross-project changes and shared tooling. In return you take on two engineering problems that grow with the repo: keeping CI fast when any change could touch anything, and keeping merges from serializing on a single main branch. Solve those two and a monorepo scales to thousands of engineers.

A monorepo is not a monolith

The two words get used as if they were the same choice, and they are not even the same kind of choice. A monolith is about deployment: how many things you ship and run in production. A monorepo is about source layout: how many repositories you keep the code in. They are independent axes.

That independence is why all four combinations exist in the wild. A modular monolith lives happily in a monorepo. So do a thousand microservices, which is roughly what Google and Meta do. A single service can be split across many repositories, and often is by accident. Deciding to use a monorepo does not commit you to a monolith or to microservices. It commits you to keeping the code together and dealing with the CI and merge coordination that follows.

flowchart TD
  Repo["one repository"]
  Repo --> A["apps/web"]
  Repo --> B["apps/api"]
  Repo --> C["packages/ui"]
  Repo --> D["packages/auth"]
  A --> CI["one CI graph<br/>one merge queue"]
  B --> CI
  C --> CI
  D --> CI

  style Repo fill:#E6F8F2,stroke:#1CB893,color:#1A1D24
  style CI fill:#E6F8F2,stroke:#1CB893,color:#1A1D24

Many projects, one repository, one CI graph, one merge queue. The layout is the monorepo. What you deploy from it is a separate decision.

Why teams adopt a monorepo

The core benefit is that a change can span projects and still be one atomic commit. When an API and every one of its callers live in the same repository, renaming that API is a single pull request, reviewed once and tested once against the whole graph. In a split-repository world the same change is a coordinated dance across several PRs, each with its own review and its own version bump.

  • One source of truth. Everyone builds from the same commit. There is no question of which version of a shared library a given service is on.
  • Atomic cross-project changes. Refactors that touch many projects land together or not at all, so you never ship half a rename.
  • Shared tooling and config. One lint config, one test runner setup, one CI definition, applied everywhere instead of copied and drifting.
  • A single dependency graph. The build system knows exactly what depends on what, which is what makes affected-projects detection possible in the first place.

The monorepo tax

The benefits are why teams start. The tax is what tooling pages skip, and it is what decides whether a monorepo scales or slowly grinds to a halt. It shows up in two places.

CI cost that grows with the repo, not the change

The naive pipeline runs every test on every change. That is fine for the first dozen projects and painful somewhere past fifty, when a one-line fix rebuilds and re-tests the entire repository. The fix is affected-projects detection: derive the set of projects a diff actually touches and run only those. The monorepo CI guide covers how the project graph and the affected set work in Nx, Bazel, Turborepo, and hand-rolled setups.

Merge coordination on one hot main branch

Every team merges into the same branch, so every merge competes with every other. Two PRs that each passed CI against an older main can still break main when they land together, and the more contributors you have the more often that happens. A scope-aware merge queue runs an independent lane per project and tests each PR against the state main will actually have at merge time. For the product behind it, see Mergify Merge Queue.

Flaky tests deserve a mention here because a monorepo amplifies them. When one change triggers CI across many projects, a single flaky test anywhere in the affected set can fail the whole run. The flaky tests guide covers detection and quarantine so a shared suite stays trustworthy.

The tooling landscape

No single tool runs a monorepo. You assemble a build system, a package manager with workspace support, and a merge automation layer. The build system is where most of the choice lives:

  • Nx. Strong fit for JavaScript and TypeScript. Derives the project graph from imports plus explicit config, and nx affected computes what to rebuild.
  • Turborepo. Lighter-touch than Nx, popular in the JavaScript world, with turbo run --filter driving the affected set.
  • Bazel. Multi-language, deterministic, incremental. High operational cost that pays off when reproducible builds across many languages are a load-bearing requirement.
  • Pants. The Bazel-shaped option that fits Python-heavy repositories well.
  • Hand-rolled. A script that maps top-level directories to projects and reads git diff --name-only. Plenty of teams ship this for a year before they hit its limits.

The tool matters less than the discipline of declaring project boundaries. Once the boundaries exist, any of these can compute the affected set, and a merge queue can reuse the same boundaries to run parallel lanes. Mergify sits at that merge layer and works with whatever build system you already run.

Do you need a monorepo yet?

It is a real tradeoff, not a default. Here is where each side lands.

Maybe wait

A monorepo may be overhead

  • A handful of services owned by one team that rarely change together.
  • No build system in place to compute an affected set, and no appetite to add one yet.

Separate repositories keep the tooling simple until cross-project changes start to hurt.

It fits

A monorepo starts paying off

  • Changes routinely span several projects and coordinating them across repositories is slow.
  • You can invest in affected-projects CI and a scope-aware merge queue to pay the tax.

With those two in place, one repository scales further than most teams expect.

FAQ

What is a monorepo?

A monorepo is a single version-controlled repository that holds many projects, services, or packages that are developed, tested, and released together. It is a choice about how you lay out source code. It says nothing about how you deploy: a monorepo can ship one deployable or a hundred.

Is a monorepo the same as a monolith?

No, and conflating them causes a lot of confused architecture debates. A monolith is a deployment choice (one thing you ship and run). A monorepo is a source-layout choice (one place you keep the code). You can run a modular monolith out of a monorepo, and you can run hundreds of microservices out of one too. Google and Meta both keep most of their code in a monorepo while deploying thousands of independent services.

Why do companies like Google and Meta use monorepos?

One source of truth makes cross-project changes atomic: rename an API and update every caller in the same commit, with one review and one CI run. Shared tooling, shared dependencies, and easier large-scale refactors all follow from having everything in one place. The cost is that CI and merge coordination have to be built to scale, which is why these companies invested heavily in build systems and merge automation.

What are the downsides of a monorepo?

Two costs dominate. CI gets expensive if every change rebuilds and re-tests the whole repo, so you need affected-projects detection to run only what a change touches. And merges serialize on one hot main branch, so a busy monorepo needs a merge queue that runs independent lanes per project. Tooling maturity matters too: a hand-rolled setup works for a while before the graph gets big enough to need Nx, Bazel, or similar.

What tools do you need to run a monorepo?

At minimum, a way to compute which projects a change affects (Nx, Turborepo, Bazel, Pants, or a script that maps directories to projects) and a CI setup that uses that information to skip untouched projects. Past a certain team size you also want a scope-aware merge queue so unrelated PRs do not serialize behind each other. Package managers with workspaces (pnpm, yarn, Cargo) handle intra-repo dependency wiring.

How does CI work in a monorepo?

The naive setup runs the full suite on every change, which stops being affordable somewhere past a few dozen projects. Monorepo CI computes the affected set from a diff and runs only the tests for the projects that changed, then runs independent projects in parallel. See the monorepo CI guide for the patterns and tradeoffs.

Do you need a merge queue for a monorepo?

Once several teams merge into one main branch, yes. A single hot branch means every merge competes with every other, and two PRs that each passed CI can still break main together. A scope-aware merge queue runs a lane per project so a frontend PR does not wait behind a backend migration, and tests each PR against the state main will actually have at merge time.

A merge queue built for monorepos.

Mergify runs independent merge lanes per scope, batches within a scope, and tests every PR against the actual future state of main. Built for teams whose monorepo outgrew running everything on every change.