How to organize apps, shared packages, and CI pipelines so teams can move fast without stepping on each other.
I've watched two teams adopt a monorepo in the same year. One of them got faster. The other spent six months blaming Turborepo for problems Turborepo had nothing to do with. Same tools, opposite outcomes — and the difference had almost nothing to do with the tooling and almost everything to do with how clearly they'd thought about boundaries before they started.
That's the lesson I keep relearning. A monorepo — one repository holding multiple apps and shared packages instead of scattering them across many repos — is an organizational decision dressed up as a technical one. The tools are mature and mostly interchangeable. The hard part is people: who owns what, who can change what, and how fast someone can validate a change without waiting on the whole world. So before I reach for any tool, I start with the boundaries.
Start with boundaries, not tools
Most monorepo pain is not from Nx, Turborepo, or pnpm. It comes from unclear boundaries between domains. Define your package contracts first, then let the tooling enforce them — not the other way around.
Here's what that looks like in practice. Before I split anything into packages, I ask what each piece is for and what it promises to everyone else. A ui package promises a set of components with stable props. An auth package promises a way to check who's logged in. The promise — the public surface — is the contract, and everything behind it should be free to change without anyone noticing. When that's clear, the tooling has something real to enforce: import boundaries, dependency rules, build graphs. When it's not clear, you get the thing that actually kills monorepos — a tangle where touching one package mysteriously breaks three others, because the "boundaries" were never boundaries at all, just folders that happened to be next to each other.
So I resist the urge to start by configuring the build system. The build system is downstream. Get the contracts right first, and Turborepo becomes a tool that enforces your good decisions instead of a scapegoat for your unmade ones.
Make ownership visible
Give each package a maintainer, an expected response time, and a clearly supported API surface. A simple CODEOWNERS file plus a short release note per package does more to prevent hidden coupling than any amount of clever tooling.
The failure mode I'm guarding against here is the orphaned package — the shared library that everyone depends on and nobody owns. It's the one where a change sits in review for a week because no one's sure they're allowed to approve it, and then someone gets impatient and reaches around the public API to import some internal file directly, "just this once." That import is how coupling sneaks in. Multiply it across a year and your clean package graph quietly turns into spaghetti with extra steps.
Visible ownership fixes this cheaply. When CODEOWNERS says who reviews changes to a package, reviews get unblocked. When each package has a one-paragraph note on what's supported and what isn't, people know which doors are theirs to use and which are private. It's not bureaucracy — it's just making the social contract legible so people don't have to guess.
Optimize for local loops
The single biggest driver of velocity in a monorepo is how fast an engineer can change one thing and know it worked. Use cached typecheck and lint tasks, plus focused test selection, so touching one package means validating in seconds — not waiting minutes for the whole repo to grind through CI.
This is where good monorepo tooling genuinely earns its place. The whole promise of Turborepo or Nx is the build graph: they know which packages actually changed and which tests actually depend on them, so they can skip everything else and reuse cached results for the parts that didn't move. Configured well, an engineer who edits one component runs the checks for that component and its dependents, gets a green light in seconds, and stays in flow. Configured poorly — or not at all — every tiny change kicks off a full-repo rebuild, and you've taken the worst part of a big codebase and made it everyone's default.
I treat the local loop as a number to protect. If validating a one-line change starts creeping from seconds toward minutes, that's a regression worth fixing, the same as any performance bug — because that's exactly what it is. Slow feedback doesn't just waste time; it changes how people work. They batch up changes, stop running checks locally, and start finding out about breakage in CI instead of at their desk. Keep the loop fast and all of that stays healthy on its own.
Monorepos succeed when they reduce coordination cost, not when they centralize everything.
That quote is the whole philosophy, honestly. The point of putting everything in one place isn't tidiness — it's to make collaboration cheaper: easier to share code, easier to change things across boundaries, easier to keep everyone on one version of the truth. The moment a monorepo starts adding coordination cost instead of removing it — slow builds, murky ownership, boundaries no one trusts — it's working against you, and no amount of tooling will save it.
So if you're standing at the start of one of these, resist the urge to spend your first week picking between Nx and Turborepo. Spend it drawing the boundaries and naming the owners. The tools will still be there on Monday, and they'll work a whole lot better once you know what you're asking them to enforce. Good luck — it's genuinely worth getting right.