The Question Most Small Teams Ask Too Early
Four developers. One Rails app, one React admin panel, a shared package of TypeScript types, and a Slack thread that has been arguing about repository layout for nine days.
Nothing has shipped since Tuesday.
That thread is the problem. The repository layout is not.
Teams of three to eight people ask this constantly, usually after reading something about how Google does it and quietly worrying they set everything up wrong. The short answer: if you ship one deployable artifact from one language, you already have a monorepo, and no tooling decision is waiting on you.
The question gets real later. This post is about where later is, what changes when you get there, and which of the four common tools earns its setup cost.
What a Monorepo Actually Is
A monorepo is one version-controlled repository holding several distinct projects that build and release independently. One history, one dependency graph, many packages.
A polyrepo (most European teams say multi-repo) splits those projects into separate repositories. Each gets its own history, its own CI configuration, its own release cadence.
Neither choice says anything about how the software runs. A monorepo can hold twelve separately deployed services. A polyrepo can hold one monolith pointlessly sliced across three repositories.
Repository layout and deployment architecture are different decisions. Conflating them is where most of the bad advice on this topic starts.
And one repository holding one application is not a monorepo. That is a repository.
The Trigger Is Shared Code Between Two Deployables
Here is the line: you need monorepo tooling when two or more independently deployed things share code that changes often.
Everything before that line is handled by git and a package.json. Everything after it turns into version-skew bugs, and those are the expensive kind.
A typical case. A web app and a mobile backend share an authentication library. Someone adds a claim to the token format, publishes 2.3.0, updates the web app, and forgets the backend.
Production breaks four days later. The commit that caused it looks completely harmless on its own.
In a monorepo that failure dies before merge, because the change and both of its consumers live in one commit and one test run. In a polyrepo you catch it with contract tests, a release checklist, and discipline that someone skips at 18:00 on a Friday.
That is the entire argument. Everything after this is tooling.
So what does the line look like in practice? Two deployables and one shared package is the moment to start looking. One deployable and five internal folders is not.
What Google Proves and What It Doesn’t
Google’s monorepo is the reference everybody reaches for, and it is the worst possible model for a team of six.
The January 2015 snapshot published in Communications of the ACM described roughly 2 billion lines of code across 9 million source files, 86 TB of data, and about 40,000 commits on a typical workday, 24,000 of them from automated systems.
To make that work, Google wrote its own version control system, its own build tool, its own virtual filesystem, and an automated refactoring pipeline that rewrites an API across the whole tree in one change.
You have four developers and a GitHub Team plan.
The lesson from Google isn’t “use a monorepo.” It’s that a monorepo past a certain size requires a build system that understands your dependency graph and only rebuilds what changed. Google wrote one. Everyone else picks one off the shelf, which is why the tool question matters more than the layout question.
Nx, Turborepo, Lerna and Bazel: What Each One Is For
| Nx | Turborepo | Lerna | Bazel | |
|---|---|---|---|---|
| Built for | JS/TS workspaces, deep framework plugins | JS/TS task running and caching | npm versioning and publishing | Any language, very large repos |
| Setup cost | Medium. Generators and plugins do a lot for you | Low. One config file and you are running | Low, sitting on top of Nx | High. Weeks, not days |
| Caching | Local plus remote via Nx Cloud | Local plus remote, Vercel-hosted or self-hosted | Inherited from Nx | Local plus remote, hermetic builds |
| Current line, Sept 2026 | 23.x | 2.11.x | 10.x | 9.x, with 8.x still shipping releases |
| Right for a team under 10? | If you want the whole workspace toolkit | Usually yes | Only if you publish packages to npm | Almost never |
Turborepo is the default answer for JavaScript and TypeScript projects at this size. You write a turbo.json describing which task depends on which, and you get a content-addressed cache that skips work already done. A developer can learn it in an afternoon.
Nx is the bigger machine. Generators, framework-aware plugins, an explicit project graph, module boundary rules that fail the build when the admin app imports from the billing internals.
Teams that like structure love it. Teams that want to be left alone find it opinionated. Both reactions are correct, which is why “Nx or Turborepo” has no universal answer.
The two overlap more than the marketing suggests. Both build a task graph, both cache locally and remotely, and both skip work that hasn’t changed. Migrating between them is a config-file problem, not an architecture problem.
Bazel is a different category entirely. It is the open-source descendant of Google’s internal build tool, and it is genuinely excellent at multi-language repositories with tens of thousands of targets.
It will also eat a month of your quarter. As of September 2026 the 9.x line is current while the 8.x line still ships releases, the latest in August, which tells you how conservatively large Bazel shops move.
If nobody on your team has run it before, don’t start now.
Be honest about Lerna
Lerna is still listed alongside Nx and Turborepo in most comparison posts as though the three compete. They don’t, and the history matters.
In April 2022 a pull request made Lerna’s unmaintained status prominent in its own README. Weeks later Nrwl, the company behind Nx, took over stewardship rather than let the project die.
The npm release record tells the story cleanly: 4 releases in 2020, 1 in 2021, then 30 in 2022 after the handover. 24 in 2023, 11 in 2024, 8 in 2025, and a handful so far in 2026 with 10.0.1 landing in August.
So Lerna is alive and shipping. It is also no longer its own task runner. Lerna’s own documentation states that it uses Nx to detect packages and the dependencies between them, and defers to Nx’s task runner for running scripts, caching, and distribution.
Which means the real choice is Nx or Turborepo, and Lerna is a versioning-and-publishing layer you add on top when you publish packages to npm. Pick it for that job or skip it.
When Polyrepo Is Still the Right Call
The monorepo argument is strong enough that it gets oversold. Separate repositories win in several ordinary situations:
- Hard ownership boundaries you actually need to enforce. Different teams, different on-call rotations, different approval rules. A CODEOWNERS file in a monorepo is a convention. A separate repository is a wall.
- Client or regulatory separation. If a customer contract says their code lives in a repository only three named people can read, that is the end of the discussion.
- Genuinely independent release cycles with no shared code. A marketing site and a billing service have nothing to say to each other. Putting them in one repo adds CI noise and buys nothing.
- Anything you open source. Extracting a public package out of a private monorepo later is a bad afternoon. Start it separate.
None of these is about scale. They are about boundaries, and boundaries are an organisational fact before they are a technical one.
The Costs That Show Up in Month Three
Nobody warns you about these in the setup tutorial.
CI minutes go up before they go down. A naive monorepo pipeline runs every test on every push, so your ten-minute build becomes forty. Affected-graph detection fixes this, which is exactly what Nx and Turborepo sell, but you have to configure it deliberately.
Code review gets noisier. Every pull request touches a repo that everyone watches, so notification fatigue arrives fast. Path-based review assignment is not optional past about eight people.
Git operations slow down eventually. Not at your size. At several hundred thousand files you start needing partial clone and sparse checkout, and that is well past the point where you would have noticed.
The one that actually bites small teams: a monorepo makes it trivially easy to couple things that should stay apart. When importing across package boundaries is one line, somebody will do it, and six months later the “independent” services share fourteen internal modules. Enforced module boundaries exist for a reason.
We covered the same failure mode from the data side in our guide to building a multi-tenant SaaS platform, where the cheapest isolation pattern is the one that leaks the moment a single query forgets its tenant scope.
What We’d Actually Do
Four questions, in this order:
- How many separately deployed artifacts do you have? One means you are done reading. Two or more, keep going.
- Do those artifacts share code that changes more than monthly? If not, separate repositories cost you nothing. If yes, a monorepo pays for itself within a quarter.
- Is everything in one language ecosystem? JavaScript and TypeScript, pick Turborepo, or Nx if you want the structure. Mixed ecosystems with a small team, keep the repositories separate until the pain is real.
- Has anyone here run Bazel in production? If no, the answer is no.
Start with Turborepo if you’re on JS or TS. Move to Nx when you find yourself wanting generators and module boundary enforcement, which is a real and reversible upgrade rather than a rewrite. Reach for Bazel when you have a polyglot repository large enough that a dedicated build engineer is already on the roster.
And if you’re a team of four still arguing in that Slack thread: pick one repository, put both projects in it, and get back to shipping. You can restructure a nine-month-old repository in a weekend. You can’t get those nine days back.
The same logic applies to most infrastructure choices at this size, which we worked through in choosing the right tech stack. Boring and well understood beats exciting and unmaintainable, every time.
Arguing about repository structure instead of shipping? Let’s talk it through. We’ll look at what you actually deploy, what actually shares code, and tell you honestly whether the tooling is worth it yet.

