Monorepo or many repos? One real rule, and a lot of false friends
People want a checklist for this: five rules to decide whether a package goes in the monorepo or gets its own repository. I went looking for that checklist while building Genesys UI, a design system split across a dozen packages. What I found instead was that there is essentially one rule, a single principle of segmentation, and a surprising number of false friends — reasons that feel solid and technical, and that quietly lead you to the wrong answer.
The one rule: what changes together lives together
This is the queen, and almost everything else is noise. If you cannot make a meaningful change to package A without also changing package B, they are not two packages that happen to depend on each other — they are one unit of change wearing two names. Put them in the same repo.
The test is change, not anything downstream of it. Not how they build, not how they ship, not who owns them. Ask only: when this moves, what else is forced to move with it? Whatever moves together, lives together.
The principle: segment by how it's consumed
The other half of the answer is not about change but about the consumer. The moment someone consuming your library — framework, ecosystem, whatever — can take one part and deliberately leave another behind, those parts are different packages.
That is the real reason the grid that became Grid Layout earned its own boundary: a consumer can want it without wanting anything else. Segment by the seams in consumption, and then — if you design those packages to be consumed by composition, snapping together rather than swallowing each other whole — you don't get a pile of libraries. You get an ecosystem.
The false friends
Everything below looks like a reason. None of it should decide the question.
"They release together." Seductive because it feels technical and concrete: two
packages always go out in the same version bump, so surely they belong together. But
a shared release cadence is downstream of the real rule, not a cause — and often
it's just a habit. Our icons lived in the React monorepo and shipped with every
react release, which made them look like a part of it. The moment it was clear
other environments would consume them too — styles, a Vue app — changes started
landing in the icons that didn't touch react at all. The joint release had never
been a dependency; it was an accident of who used them first.
The lesson runs deeper than one package: if something can be consumed on its own, let the architecture say so from day one. Splitting out a thing only React uses feels artificial early — but you're building the structure the project actually demands, not the one you happen to need this quarter, and that foresight is almost always repaid.
"They release independently." The mirror image, and just as wrong. Packages publish independently whether they live in one repo or ten — repository layout and publishing cadence are orthogonal. So "they ship on their own schedule" is not an argument for splitting them into separate repos any more than the opposite is an argument for merging them. Used as a reason, it only induces error.
"Different teams own them." Tempting, and almost always a mistake. We build software trying to cast solid, lasting blocks; the way we group people is ephemeral — it answers to business and coordination, and it gets reshuffled. We went through several reorganisations, and the pattern repeated every time: a team had built a monorepo around its project for its own convenience, and the moment part of that project moved to another team, the bill came due — either refactor the code to stand on its own, or coordinate two teams around one team's way of working. Friction the software never needed, manufactured entirely by an org chart.
Picture the common case: one team keeps its components and its application together in a single monorepo. The day a separate team forms to own the shared components, that arrangement fights them — and it ends the only way it can, with the generic components split out onto whatever foundation the new team picks, while the app-specific ones stay with the app. All of it was visible from the start. Had the original team kept the generic components independent from day one, with the app-specific pieces in their own monorepo, the reorg would have been a change of ownership, not a rewrite. Projects should be team-agnostic: shaped by the software, never by this quarter's org chart.
When structure does end up carving your packages anyway, at least be honest that you are no longer doing what's best for the software — and keep enough freedom of movement to undo it when the next reorg comes.
"It would cost too much in tooling." In practice this does shape architecture; it still shouldn't. And the cost is mostly a myth: a dedicated server to run the CI and automation for a whole project can cost from around €60 a year. So either the tooling cost is far too weak a reason to bend your architecture around, or your setup is wildly oversized. Neither is a good place to make a structural decision from.
So, the whole checklist
One rule: what changes together lives together. One principle: segment by how it's consumed, and consume by composition. Everything else — shared releases, independent releases, team boundaries, CI cost — is a false friend wearing the costume of a rule.
Get the one rule right and design for composition, and the packages stop fighting you. That's the difference between a directory full of libraries and an ecosystem. I dig into the layers and the bill that comes with them in Architecting a design system as a multi-package ecosystem.