The pattern
A single repository contains multiple deployable units — usually a frontend, a backend, shared utility packages, sometimes a mobile app and infrastructure code. Tools like pnpm workspaces, Nx, Turborepo, or Bun workspaces manage the dependency graph.
The alternative is polyrepo: each project in its own Git repository, published as separate packages.
When monorepo wins
- You share code across multiple deployables (typed API client, shared validators, design system)
- You want atomic commits that touch multiple packages (rename a field across backend + frontend in one PR)
- Team size is 3+ and people work across packages
- CI/CD can build the dependency graph (Turborepo, Nx)
When monorepo loses
- Single deployable, no shared code, no plans to add either soon
- Team of 1–2 working on independent projects
- Languages don't compose well (a Rust binary and a Python script don't benefit from sharing a repo)
- Build tooling expertise isn't there — a misconfigured monorepo is slower than two separate repos
The defaults we use
For SaaS products with a Next.js app + maybe a mobile companion + shared types: monorepo (pnpm workspaces + Turborepo). For an MVP that's just a Next.js app: single repo, no monorepo overhead.
The instinct to monorepo "for future scaling" is usually wrong at v1. Adopt it when you have the second package.
Cost of getting it wrong
A junior-level monorepo setup with no caching adds 20–60 seconds to every CI run. Across a sprint that's hours of wasted developer time. A properly configured one with build caching is faster than a polyrepo for any cross-package change.


