Why we run on monorepos again
After three years of polyrepos, the cost of context switching outgrew the cost of one big lockfile. Here is what we kept, what we changed, and the tooling that finally made it work.
Anders Lindqvist
Principal Engineer
In 2023 we split everything. Twelve services, twelve repositories, twelve pipelines — the textbook picture of team autonomy. By late 2025 we were spending our Fridays writing version-bump PRs and our Mondays debugging which combination of those bumps had broken staging. This is the story of going back, and why it stuck this time.
What polyrepos actually cost us
The pitch for polyrepos is isolation: small blast radius, independent deploys, clear ownership. All true. What the pitch leaves out is that isolation is also a tax on every change that crosses a boundary — and in a product company, the changes that matter almost always cross a boundary.
- A shared type change touched five repos, five PRs, five reviews — and landed over three days, in the wrong order, twice.
- Refactoring became archaeology:
git blamestopped at the repo wall, so nobody knew why the contract looked the way it did. - Each repo grew its own lint config, CI quirks, and Node version. “It works in checkout-service” became a sentence we said out loud.
We optimized for the org chart we wanted and got the integration costs we deserved.
Sprint retro, November 2025
The three tools that changed the math
Monorepos failed for us in 2022 because every commit rebuilt the world. The tooling that exists now makes the big repo behave like many small ones — without the walls. Three pieces did the heavy lifting: task-graph caching, affected-only CI, and workspace constraints that encode the dependency rules we used to enforce with repository boundaries.
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "test/**"]
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"cache": false
}
}
}
With remote caching on, a one-line change in the docs package builds in eleven seconds. The full cold build is still four minutes — we just stopped paying it on every push. CI runs turbo run test --filter=...[origin/main] and touches only what the diff touches.
What we kept from the polyrepo years
Going back to a monorepo did not mean going back to a monolith. Service boundaries survived; only the repository walls came down. Ownership lives in CODEOWNERS instead of separate remotes, and deploys stayed independent — the task graph knows which services a commit reaches, and only those ship.
The one rule we never break
Trunk stays green. A red main in a monorepo blocks forty people, not four — so merge queues and affected-only checks are not optional extras, they are the deal you sign when you co-locate the code.
The verdict, six months in
Cross-cutting changes went from days to hours. Onboarding went from “clone these nine repos and pray” to git clone && pnpm i. And the Friday version-bump ritual is gone — there are no versions between our own packages anymore, only the current commit. The lockfile is enormous. We have made peace with that.
Anders Lindqvist
Principal Engineer
Builds platforms and the teams that run them. Twenty years of shipping, ten of un-shipping other people’s microservices.
READ NEXT
-
Engineering
Observability from day one (not day ninety)
The cheapest moment to wire OpenTelemetry is in the same PR that ships the route. A short rationale and a copy-paste starting kit.
Anders Lindqvist · Mar 31, 2026
-
Uncategorized
Hello world!
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
· Jul 17, 2026
-
Design
Design tokens that survive engineering handoff
Most token systems break the moment a developer needs a hover-active-disabled triplet. A short field guide to naming, composition, and the four rules we never break.
Mira Holmberg · May 19, 2026