Always Go With a Monorepo
First published at Wednesday, 15 July 2026
Always Go With a Monorepo
This is a blog article out of a series where I reflect on what I learned during funding, growing (, and selling) SaaS product companies. While I write those things down for myself, since I believe self-reflection helps me learn, I also want to share my thoughts with anybody who might be interested. An overview of all related topics can be found below. I have a bunch of these blog posts lined up, so you also might want to follow me if you like this one.
I made this argument to my team again last week, as I have many times over the years. This time a team right next to us was busy doing the opposite: taking a working monorepo and splitting it into around eighty separate repositories. I think it will cost them dearly, and slowly, in a way that will be hard to attribute to the decision once the friction sets in.
So let me state the position as flatly as I hold it. Use one repository. Draw your boundaries inside it. Reach for a second repository only when you have a mechanical reason the filesystem genuinely cannot give you, and notice how rarely that turns out to be true.
You want a boundary, not a repository
When somebody proposes splitting a repository, listen to the reason. It is almost always about a boundary: this part should be isolated from that part, this team should own this and not touch that, this layer is reusable and that one is specific. These are good instincts. A system without clear internal boundaries turns to mud.
The mistake is the jump from "I want a boundary" to "I want another repository". A repository boundary is one way to draw a line, and it happens to be the most expensive one available. A directory is also a line. So is a module, a package, a review rule. The question is never whether you need a boundary. You do. The question is what the boundary has to do, and whether a repository split is the cheapest thing that does it. It almost never is.
It is the same reflex that drives teams to split a system into microservices before they need to, which I have written about separately: reaching for an expensive, hard-to-reverse structural boundary to get an isolation you could have had for almost nothing inside a single deployable. Repositories and services are different lines, but the error is identical.
Repository count is not an architecture decision
The number of repositories you have is not an architectural property of your system. The boundaries inside it are. You can take one well-structured codebase and split it across ten repositories without changing its architecture at all, only its logistics, and you will have made those logistics much worse.
Most advice on this hedges by scale: a monorepo for the big companies, separate repositories are fine for a small team. I think that gets it backwards. Google and Meta run enormous monorepos and pour real engineering into the tooling to keep doing so. The usual reading is "they are special, do not copy them". The more useful reading is that the coordination cost of splitting code across repositories is real enough that the largest engineering organisations on earth chose to fight tooling problems rather than pay it. If the polyrepo tax is heavy enough to bend Google toward one repository, it is certainly heavy enough to matter at your scale. Small teams have less slack to absorb that overhead, not more.
The cheap boundary: a directory and a review
In my current work we build a multi-tenant platform as a single repository, and the boundary that matters most, the line between what is reusable across tenants and what is specific to one, is drawn with top-level directories. A reviewer can tell from the file path alone which side of the line a change sits on. When something tenant-specific starts leaking into the reusable part, the path itself is the smell, visible in the diff before anyone reads a line of code.
That costs nothing. There is no second checkout, no version to pin, no release to coordinate. The boundary is as visible as a repository wall would have made it, and it moves with a simple file move when you get it wrong.
I will be honest about the trade-off, because it is the one real argument for splitting. A directory boundary is enforced socially, by review and convention, and at most by a CI check. Nothing at the language level stops a developer from importing across the line until something catches it. A separate repository enforces the same boundary mechanically: you cannot import what you have not declared as a dependency. That is a genuine difference. But you buy that one guarantee by paying the entire coordination tax below, and for an internal team that tax is the worse deal almost every time.
What the eighty-repository team actually bought
It helps to be concrete about the cost, because it does not arrive as one big failure. The team that split into eighty repositories now pays in small, constant ways. A change that touches a shared concept is no longer one pull request. It is several, across several repositories, that have to be merged in the right order. Each repository pins versions of the others, so there is now a matrix of which versions are deployed together and which combinations have actually been tested. A refactor that would have been one atomic commit becomes a coordination project. Coding style and ownership drift apart repository by repository, because nothing holds them together any more.
None of these is fatal on its own. Together they are a tax on every cross-cutting change you will ever make, and cross-cutting changes are exactly the ones that move a product forward. Velocity does not collapse. It bleeds.
The argument the old debates could not make
There is one more reason this position ages well, and it is new. A coding assistant working in a single repository sees the whole dependency graph. Ask it to rename a concept, change an interface, or trace where something is used, and it can do it across the entire system in one atomic change, because every call site is in front of it. Split that system across eighty repositories and the same task fragments into eighty coordinated changes, most of whose call sites the assistant cannot even see from where it is working.
The boundary you wanted is still available without that cost. A per-directory context file, a CLAUDE.md in a subtree, lets you encode area-specific rules that the assistant loads on demand, so a part of the tree can carry its own constraints without being cut out of the tree. You get scoped rules and a whole graph at the same time. The repository split gives you scoped rules and a shredded graph.
I have argued before that most of what we now do to let agents work, the part people have started calling context engineering, is not a new demand the tools invented but engineering discipline we should have had all along, and that some of us always did. A coherent view of the whole system and explicit, written-down rules for each part of it are exactly that. Keeping one repository, with per-area context where it helps, is another instance: it was good practice before any assistant existed, and the assistant only makes the cost of ignoring it more obvious.
Summary
Use one repository, regardless of how big the team gets. The number of repositories is logistics, not architecture, and the architecture, the boundaries, is better served by directories, modules, and review than by a repository wall. A split buys you one thing, mechanical enforcement of a boundary, at the price of cross-repository pull requests, version pinning, a deployment matrix, drifting style, and a dependency graph no human or assistant can see whole. Draw the line in the filesystem. Reach for a second repository only when something truly mechanical forces your hand.
Subscribe to updates
There are multiple ways to stay updated with new posts on my blog: