Skip to main content

Monorepo Patterns

Quick Summary

A monorepo (mono-repository) stores multiple projects, packages, or services in a single Git repository. Companies like Google, Facebook, and Microsoft use monorepos for thousands of projects. This approach simplifies dependency management, code sharing, and atomic changes — but requires specialized tooling at scale.


Monorepo vs Multi-Repo

flowchart TD
subgraph "Multi-Repo"
R1["frontend<br/>(github.com/co/frontend)"]
R2["backend<br/>(github.com/co/backend)"]
R3["shared-lib<br/>(github.com/co/shared-lib)"]
R1 -.->|npm dependency| R3
R2 -.->|npm dependency| R3
end
subgraph "Monorepo"
M["github.com/co/platform"]
M --> P1["packages/frontend/"]
M --> P2["packages/backend/"]
M --> P3["packages/shared-lib/"]
end
AspectMonorepoMulti-Repo
Code sharingImport directly, always latestPublish + version + install
Atomic changesOne PR changes frontend + backend + libThree PRs across three repos
Dependency managementSingle lockfile / consistent versionsPer-repo, can drift
CI/CDComplex (what to build?)Simple (build everything)
Access controlEveryone sees everything (by default)Per-repo permissions
Repository sizeCan grow very largeEach repo stays small
OnboardingClone once, see everythingClone many repos

Monorepo Project Structure

my-platform/
├── .github/
│ └── workflows/ # CI/CD for all packages
├── packages/
│ ├── frontend/
│ │ ├── package.json
│ │ └── src/
│ ├── backend/
│ │ ├── package.json
│ │ └── src/
│ ├── shared-utils/
│ │ ├── package.json
│ │ └── src/
│ └── docs/
│ ├── package.json
│ └── src/
├── package.json # Root workspace config
├── turbo.json # Turborepo config (or nx.json)
└── pnpm-workspace.yaml # Workspace definition

Monorepo Tooling

ToolEcosystemKey Feature
TurborepoJavaScript/TypeScriptRemote caching, task pipelining
NxMulti-languageDependency graph, affected detection
LernaJavaScript (legacy)Package publishing
BazelMulti-language (Google)Hermetic builds, massive scale
PantsPython, Go, JVMIncremental, dependency-aware

Git Techniques for Monorepos

Sparse Checkout (Clone Only What You Need)

# Clone without files
git clone --no-checkout git@github.com:company/platform.git
cd platform

# Enable sparse checkout
git sparse-checkout init --cone

# Check out only the packages you need
git sparse-checkout set packages/frontend packages/shared-utils

# Now only those directories are on disk
ls packages/
## frontend/ shared-utils/

Shallow Clone + Sparse

# Fastest possible clone for CI
git clone --depth 1 --filter=blob:none --sparse git@github.com:company/platform.git
cd platform
git sparse-checkout set packages/backend

Path-Based CI (Only Build What Changed)

# Check which packages changed (for CI)
git diff --name-only HEAD~1 | grep "^packages/" | cut -d'/' -f2 | sort -u

When to Use a Monorepo

ScenarioMonorepo?
Tightly coupled services sharing code✅ Yes
Frontend + backend sharing types/schemas✅ Yes
Many small packages under unified CI✅ Yes
Completely independent projects❌ Separate repos
Open-source project with many contributors❌ Usually separate
Very large binary assets❌ Git handles this poorly

Best Practices

  • Use workspace tooling (Turborepo, Nx) — don't manage monorepos manually
  • Enable sparse checkout for large repos — developers shouldn't need all projects
  • Path-scoped CI — only test/build what actually changed
  • Shared linting/formatting — enforce consistency at the root level
  • CODEOWNERS file — define who reviews which directories

What's Next

  1. GitHub Actions & CI/CD — Automate builds and deployments
  2. Submodules & Subtrees — Alternative approaches to multi-project Git