Monorepo

Concept

Keeping the code for several projects in a single repository with one shared history, so that shared code can be changed everywhere in one edit.

Why it exists

One product turns into several codebases quickly. There's the web interface, the mobile app, the server, and the set of buttons, colors, and icons all three of them use. The usual arrangement gave each of those its own repository — the space holding code together with its full history of changes.

Once they were split up, every change to the shared code triggered the same ritual. Trimming two pixels of padding off a button meant editing the shared repository, publishing it with the version bumped from 0.4.2 to 0.4.3, then opening the web, app, and server repositories in turn to raise that number and collect three separate reviews. A one-line edit generated four separate requests, and getting all four of them reviewed and merged took two days.

The in-between state was the more irritating part. For a few days the web would be on 0.4.3 while the app was still on 0.4.2, and during that window the same button looked different in two places. Finding out which project hadn't upgraded yet meant opening all four repositories and reading the version number in each one.

A monorepo removes that ritual. Put the pieces back into one repository and the button edit and the three places that consume it collapse into a single change with a single review. Passing version numbers between teams stops being a thing anyone does.

How it works

Inside one repository, projects are separated into folders, usually split between things that run and things that get shared.

  1. The folder for things that run holds the web app, the mobile app, and the server. Each one still builds on its own and deploys on its own.
  2. The folder for shared things holds design components, common functions, and the files describing the shape of API requests and responses. An API is the agreed interface programs use to talk to each other.
  3. When a running project uses a shared one, it points at a folder in the same repository rather than at a published version number. Change the shared code and all three pick up the change immediately.

Stop there and the repository gets slower as it grows. Rebuilding every project and running every test for a one-line change turns five minutes of waiting into forty. So a monorepo comes with a layer of tooling on top, and that tooling does two things.

The first is mapping what depends on what. If the tool knows the design components are used by the web and the app but not by the server, then editing a component re-tests the web and the app and skips the server entirely.

The second is remembering results. If something was already built from exactly this code, the stored result gets reused instead of rebuilt. Nobody re-runs the checks a colleague already ran, and a repository holding twenty projects only spends time on the two or three that actually changed.

Where it shows up

The deciding factor isn't how many repositories you have. It's how often a change crosses a boundary between them. Frequent crossings make keeping things together cheaper; rare ones make splitting cheaper.

Three situations favor keeping them together.

  1. Several teams share the same components. The standard case is a design system whose buttons and input fields are used by the web interface and the mobile app alike.
  2. The server and the interface share a contract. When a response shape changes the interface code has to change with it, and in one repository both go into the same change and get checked together. Split apart, the server ships first and the screen breaks.
  3. The team practices something like trunk-based development, merging into the main line several times a day. The more often you merge, the more the cross-repository waiting hurts.

Splitting still wins in some places. Wildly different release cadences reduce the reason to share a history: mixing something that ships quarterly with something that ships five times a day only expands what has to be checked each time. The same goes for outside contractors who should see one part of the code and nothing else. Setting per-folder permissions inside one repository is more work than simply keeping two repositories.

How it sounds at work

"Let's make this a monorepo. The server and the interface need to share the same types."

A case for putting code that shares a contract into one repository so it changes together.

"We're a monorepo but the whole thing rebuilds every time. Sounds like caching was never set up."

A diagnosis that the repositories were merged while the tooling that makes the arrangement work was never installed.

"Being a monorepo doesn't mean we deploy it as one piece."

Pointing out that where code is stored and how many units get deployed are separate questions.

Pagedeck merged four repositories into one last year. The trigger was the design components. Noa moved the position of an input field's error text, the web picked it up, the app picked it up three weeks later, and two screenshots of the same screen ended up side by side in a meeting.

The first thing Theo did after merging was measure. The time to check a single change had jumped from six minutes to thirty-one the moment the repositories came together. Adding the dependency map and the build cache brought it back down to nine minutes. Deciding to merge repositories and installing the tooling are effectively one task.

What people get wrong

The most common mistake is reading it as the same thing as a monolith. Both words open with a prefix meaning one, and both describe pulling things together, which is reason enough to mix them up. But they count different things. A monorepo is about where code is stored; a monolith is about how many pieces get deployed to servers. Twenty services can live in one repository and deploy separately, and four repositories can still ship as one unit. Conflating them produces objections like "merging into a monorepo will make it hard to split later," when merging repositories leaves the deployment units exactly as they were.

The second is assuming the merge itself is the whole job. It looks like a matter of moving folders around, which sounds like an afternoon of work. In practice the repository gets slower right after the merge. The test tooling doesn't know what changed, so it runs everything, multiplied by however many projects now live there. Hold that state for a few weeks and the team starts skipping checks, merge conflicts — two people editing the same place in incompatible ways, so nothing can be merged automatically — show up more often than before, and someone proposes splitting the repositories back apart.

Easily confused with

TermWhat it keeps as oneHow it differs from a monorepo
MonolithThe unit deployed to serversCounts running pieces rather than storage locations. Neither forces the other, and all four combinations exist

Where to go next

Related terms

Last updated 2026-09-16