All posts
· 5 min read

Monorepo vs Polyrepo: A Practical Decision Framework

MonorepoToolingDX

The monorepo vs polyrepo debate generates more heat than light. Both are tools with tradeoffs — neither is universally better. Here's how I decide for each project.

The Decision Tree

Ask three questions:

  1. Do packages share more than 30% of their code? → Monorepo
  2. Do teams need fully independent CI/CD? → Polyrepo (or well-configured monorepo)
  3. Is your team < 20 people? → Monorepo (the coordination cost of polyrepo isn't worth it)

Monorepo with Turborepo

Turborepo is my current default. It caches build outputs intelligently — if package A hasn't changed, it skips rebuilding A and everything that depends on it. On a 10-package monorepo, this cuts CI time by 60-80%.

// turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "lint": {}
  }
}

Monorepo with Nx

Nx is more opinionated and feature-rich. Its computation cache is local + remote (Nx Cloud), its dependency graph visualization is excellent, and its generators scaffold code consistently. The cost: steeper learning curve and more "Nx way" of doing things.

I reach for Nx when the monorepo has 15+ packages and multiple languages (TypeScript + Go + Python). For pure TypeScript monorepos under 10 packages, Turborepo is simpler.

The Polyrepo Case

Polyrepos shine when:

  • Packages have genuinely different lifecycles (a library updated weekly vs an app deployed hourly)
  • Teams have different access control needs (open-source library vs private app)
  • You need to version packages independently for external consumers

The Hidden Cost Nobody Talks About

Monorepo pain: Tooling. IDE performance degrades, git operations slow down, and CI configuration gets complex. At Google scale (billions of lines), you need custom tooling. At startup scale, this isn't a problem.

Polyrepo pain: Dependency drift. Package A depends on utils@1.2, Package B depends on utils@1.5. Now you're maintaining two versions or doing a coordinated upgrade across 8 repos. This is the silent killer of polyrepo setups.

My Default

Start with a Turborepo monorepo. Split out repos only when you hit a concrete problem that the monorepo can't solve. This path is reversible; the opposite isn't.