A monorepo puts all (or most) of your code in a single version-controlled repository.
The monorepo vs. polyrepo debate is less about 'which is better' and more about which set of problems you'd rather have. Monorepos concentrate the pain into tooling (build systems, CI orchestration, code ownership at directory granularity) and eliminate the pain of cross-repo coordination (version bumps, integration lag, dependency drift). Polyrepos do the reverse. Companies at scale have shipped both — Google, Meta, and Uber run huge monorepos; Netflix and Amazon are largely polyrepo. The right choice depends on the shape of your dependencies, your team's discipline with tooling, and how much cross-service change is normal.
Strong signals: multiple services and libraries frequently changed together (a schema change touches API, worker, and web); shared internal libraries that need atomic updates across consumers; a small enough team that one CI pipeline can serve everyone; a desire to enforce consistent tooling (linting, testing, deployment) by structure rather than by policy. Weak signals: 'monorepos are trendy'; 'Google does it'; 'we want a single git clone.' Anti-signals: teams that need to operate on very different release cadences (a mobile app on a 2-week app-store cycle alongside a service on continuous deploy is workable but adds tooling overhead).
A monorepo without a build system that understands package graphs is a slow, angry polyrepo. Options: (1) Nx (JS/TS-first, mature caching, good DX). (2) Turborepo (lighter than Nx, JS-focused, Vercel-backed). (3) Bazel or Pants (polyglot, industrial-strength, steeper learning curve). (4) Moon or Lerna+Rush (JS ecosystem alternatives). Whichever you pick, the essential capabilities are: (a) affected-package detection so CI only runs what changed, (b) remote caching so a build/test result computed by one dev/CI job is reused everywhere, (c) task dependency graphs so builds order correctly across packages.
The naive monorepo CI runs every test on every PR and grinds to a halt at ~500 packages. The productive setup: on PR, run only the tests for affected packages (as determined by the build system's dependency graph); on main branch merges, run the full test suite as a safety net; publish a remote cache from main-branch builds so PR builds hit the cache for unchanged packages. Expect 3-6 months of continuous tuning to get PR CI under 10 minutes at moderate scale. Track: median PR CI duration, 95th percentile PR CI duration, cache hit rate. Alert when any regresses meaningfully.
Without directory-level ownership, a monorepo turns into a tragedy of the commons — anyone can edit anything, and no one takes long-term responsibility. Use CODEOWNERS (GitHub/GitLab) to require review from the owning team for changes in their directories. Make ownership granular (per top-level package or per service) but not so granular that trivial changes require five reviews. Publish an owners map in your service catalog so 'who owns this code' is always answerable in one click. When a team is dissolved or reorganized, reassign ownership explicitly — orphaned directories rot fast.
Polyrepo → monorepo: use git filter-repo or Nx's import tools to preserve history when consolidating. Migrate in waves (shared libraries first, then services that consume them, then leaf apps). Expect a 3-6 month project at moderate scale. Monorepo → polyrepo: rarer, usually driven by an acquisition or a spinout. git subtree split preserves history for the extracted portion. Both migrations underestimate the tooling and CI rework required — budget 2-3x the git-mechanics effort for the surrounding infrastructure changes.
Investor directory · Fundraising library · Articles A–Z · Company funding database