---
title: "Monorepo Patterns"
slug: "monorepo"
category: "file-organization"
tags: ["file-organization", "monorepo", "workspaces", "build-tools"]
status: "stable"
last_updated: 2026-05-14
summary: "When a monorepo earns its keep, the workspaces and build tools that work, and the hidden costs in CI and team gravity."
related: ["[[file-organization/project-structure]]", "[[file-organization/large-codebase]]", "[[file-organization/folder-hierarchy]]", "[[coding/git]]", "[[tooling/github]]"]
---

## Overview

A monorepo holds multiple shippable artifacts in one git repo. Done well, it lets a single PR refactor a shared library and every consumer at the same time. Done poorly, it becomes a slow CI graveyard where every change rebuilds the world. This page covers when the model pays off, the workspace and build tooling worth using, and the costs that show up six months in. See [[file-organization/project-structure]] for the single-repo default.

## Use a monorepo only with a concrete reason

Default to many repos. Reach for a monorepo when at least one is true:

- Two or more projects share a library that changes in lockstep. Coordinated cross-repo releases cost more than a shared CI.
- Atomic refactors across services are routine (renaming a shared type, changing a wire format).
- The team is small enough that one PR can land everywhere without three reviewers blocking.

Cargo-culting a monorepo because "Google does it" wastes a quarter. Google has dedicated build infrastructure teams; most teams have [[tooling/github-actions|GitHub Actions]] and a six-minute CI budget.

## Workspaces are the entry point

Native workspaces are the cheapest monorepo on-ramp. Reach for them before installing a third-party build graph.

- **pnpm workspaces.** Best Node story. `pnpm-workspace.yaml` lists packages; `pnpm install` symlinks them. Fast, deterministic.
- **Yarn workspaces (Berry).** Works but the v1-vs-Berry split is a tax. Prefer pnpm unless already on Yarn.
- **npm workspaces.** Functional, slower than pnpm.
- **Cargo workspaces.** A root `Cargo.toml` with `[workspace]` and `members = [...]`. Trivial. Use it.
- **Go workspaces (`go.work`).** Use for local cross-module dev; do not check in `go.work.sum`.

A workspaces-only repo carries five to twenty packages comfortably. Past that, the build graph needs help.

## Add a build graph when CI hurts

Above ~20 packages or when CI exceeds 10 minutes on a small change, add a task runner that understands the dependency graph and caches.

- **[[comparisons/turborepo-vs-nx|Turborepo]].** Best balance for Node monorepos. Config in `turbo.json`. Remote caching via Vercel or self-hosted.
- **[[comparisons/turborepo-vs-nx|Nx]].** More features (generators, code mods, project graph UI). Worth it past ~50 packages or for non-JS code.
- **Bazel / Buck2 / Pants.** Polyglot, hermetic, slow to set up. Only worth it with a dedicated build engineer.

Migration order: workspaces, then Turborepo or Nx, then Bazel. Skipping to Bazel for five packages is a footgun.

## CI sharding is mandatory at scale

A 50-package monorepo cannot rebuild everything on every push. Shard by what changed.

- Use `turbo run --filter=...[origin/main]` or Nx's affected commands to limit builds to the touched packages.
- Cache build outputs by input hash so a no-op rebuild is a cache hit.
- Split test runs across parallel CI jobs via GitHub Actions matrices.
- Tag long-running integration tests so PR CI runs the fast path and nightly CI runs the full suite.

Without sharding, the monorepo becomes the bottleneck it was supposed to fix.

## CODEOWNERS at the package level

A monorepo erases natural team boundaries. CODEOWNERS puts them back. List ownership per package directory:

```
/packages/billing/         @org/payments-team
/packages/auth/            @org/identity-team
/apps/admin-dashboard/     @org/admin-team
```

Without CODEOWNERS, the "anyone can review anything" failure mode dominates and code quality drifts package by package. See [[file-organization/large-codebase]] for the broader pattern.

## Hidden costs to budget for

The pitch is "one PR refactors everything." The bill arrives later.

- **CI complexity.** Sharding, caching, and matrix configs are a maintained codebase of their own.
- **Single-team gravity.** The repo becomes "owned" by whoever fixes its build first; other teams stop contributing.
- **Tooling locks.** A bug in the chosen build tool blocks the entire repo. Vet maintenance health first.
- **Slow git.** A 5 GB repo with 100k files makes `git status` slow without `core.fsmonitor`. Use partial clone or sparse checkout.
- **IDE indexing.** [[coding/typescript-tsconfig|TypeScript project references]] and editor performance degrade past ~50 packages.

## Poly vs mono: a deciding question

Ask: "Does a typical change touch one package or many?"

- **One package.** Polyrepo wins. Independent CI, independent ownership, independent release cadence.
- **Many packages.** Monorepo earns its keep. Atomic refactors are the killer feature.

If the answer is "depends," start polyrepo and merge later when the shared-library pain becomes the daily pain. See [[coding/git]] for the commit hygiene that makes either model tolerable, and [[comparisons/monorepo-vs-polyrepo]] for the head-to-head selection rules.

## Related

- [[file-organization/project-structure]]
- [[file-organization/large-codebase]]
- [[file-organization/folder-hierarchy]]
- [[coding/git]]
- [[tooling/github]]
