Monorepo Architecture Guide: Tools, Patterns & When to Choose One
Monorepo Architecture Guide#
A monorepo stores multiple projects, libraries, and services in a single repository. Google, Meta, Microsoft, and Uber all run massive monorepos — but the pattern works at every scale.
Monorepo vs Polyrepo#
Monorepo Polyrepo
┌─────────────────────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ apps/ │ │ repo │ │ repo │ │ repo │
│ web/ │ │ A │ │ B │ │ C │
│ api/ │ └──────┘ └──────┘ └──────┘
│ packages/ │ each has own CI, deps,
│ ui-lib/ │ versioning, and review
│ shared-utils/ │
│ .github/CODEOWNERS │
└─────────────────────┘
Monorepo Advantages#
- Atomic changes — refactor a shared library and every consumer in one PR
- Single source of truth — no version drift between packages
- Code reuse — shared utilities, types, and configs live alongside apps
- Unified CI/CD — one pipeline, one set of checks
- Easier onboarding — clone once, see everything
Polyrepo Advantages#
- Isolation — a broken build in repo A never blocks repo B
- Granular permissions — easier access control per repository
- Simpler at small scale — no monorepo tooling overhead
- Independent deploy cadence — teams ship on their own schedule
Monorepo Tooling Comparison#
| Tool | Language | Build Cache | Remote Cache | Task Graph | Plugin Ecosystem |
|---|---|---|---|---|---|
| Nx | JS/TS (+ others) | Yes | Yes (Nx Cloud) | Yes | Rich |
| Turborepo | JS/TS | Yes | Yes (Vercel) | Yes | Minimal |
| Bazel | Any | Yes | Yes | Yes | Extensive |
| Lerna | JS/TS | Via Nx | Via Nx | Limited | Moderate |
| Rush | JS/TS | Yes | Yes | Yes | Moderate |
| Pants | Python/Go/Java | Yes | Yes | Yes | Growing |
Nx#
Nx provides computation caching, affected-only execution, and a rich plugin system for React, Angular, Node, and more.
# Create an Nx workspace
npx create-nx-workspace@latest my-org
# Run only affected projects
npx nx affected --target=build
# Visualize the dependency graph
npx nx graph
Turborepo#
Turborepo focuses on speed and simplicity. It layers on top of existing package managers (npm, pnpm, yarn).
# turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {}
}
}
Bazel#
Bazel handles any language and scales to millions of lines of code. It uses hermetic builds and content-addressable caching.
# BUILD file
ts_library(
name = "shared-utils",
srcs = glob(["src/**/*.ts"]),
deps = ["//packages/types"],
)
Dependency Management#
Internal Dependencies#
Structure packages so they reference each other through the workspace protocol:
{
"name": "@org/web-app",
"dependencies": {
"@org/ui-lib": "workspace:*",
"@org/shared-utils": "workspace:*"
}
}
Version Policies#
- Fixed/locked — all packages share one version number (simpler)
- Independent — each package versions independently (more flexible)
- Hybrid — groups of related packages lock together
Dependency Graph Visualization#
@org/web-app
├── @org/ui-lib
│ └── @org/design-tokens
├── @org/shared-utils
└── @org/api-client
└── @org/shared-utils (deduplicated)
@org/api-server
├── @org/shared-utils
└── @org/db-models
Tools like nx graph or turbo run build --graph render interactive dependency graphs so you can spot circular dependencies early.
Build Caching#
Build caching is the single biggest performance win in a monorepo.
How It Works#
1. Hash inputs (source files + deps + config + env)
2. Check cache for matching hash
3. Cache HIT → restore outputs instantly
4. Cache MISS → run task, store outputs with hash
Local vs Remote Cache#
- Local cache — stored on the developer machine (
node_modules/.cache) - Remote cache — shared across the team and CI (Nx Cloud, Vercel Remote Cache, custom S3 bucket)
Developer A runs build → uploads artifacts to remote cache
Developer B runs same build → downloads cached result (seconds vs minutes)
CI runs same build → cache hit (no rebuild)
Cache Invalidation#
Caches invalidate automatically when any input changes:
- Source file modified
- Dependency version bumped
- Environment variable changed
- Build config updated
CI/CD for Monorepos#
Affected-Only Builds#
Never rebuild everything. Run tasks only for packages affected by the current change:
# GitHub Actions example
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for diff
- run: npx nx affected --target=build --base=origin/main
- run: npx nx affected --target=test --base=origin/main
Parallel Execution#
Monorepo tools understand the task graph and run independent tasks in parallel:
lint ──────────┐
build:ui-lib ──┤
build:utils ───┤──→ build:web-app ──→ deploy:web
build:api ─────┘──→ deploy:api
Selective Deploys#
Only deploy services whose code actually changed:
deploy-web:
if: contains(needs.changes.outputs.packages, 'web-app')
steps:
- run: npx nx build web-app --production
- run: deploy-to-vercel
Code Ownership with CODEOWNERS#
The CODEOWNERS file maps directories to responsible teams:
# .github/CODEOWNERS
# Platform team owns infrastructure
/infra/ @org/platform-team
/.github/ @org/platform-team
# Frontend team owns UI packages
/apps/web/ @org/frontend-team
/packages/ui-lib/ @org/frontend-team
# Backend team owns API
/apps/api/ @org/backend-team
/packages/db-models/ @org/backend-team
# Shared code requires two approvals
/packages/shared-utils/ @org/frontend-team @org/backend-team
This enforces review gates — PRs touching /apps/api/ automatically request review from the backend team.
When to Choose a Monorepo#
Choose Monorepo When#
- Teams share libraries, types, or configs frequently
- You want atomic cross-project refactors
- Consistent tooling and standards matter
- You have (or will invest in) CI/CD infrastructure
- Codebase is under ~10 million lines (or you use Bazel)
Choose Polyrepo When#
- Teams are fully independent with no shared code
- Projects use completely different tech stacks
- Strict access control is required per project
- You lack CI/CD capacity for monorepo tooling
- Regulatory requirements mandate repository isolation
Hybrid Approach#
Many organizations use a hybrid: a monorepo for tightly coupled services and separate repos for truly independent projects.
monorepo/ # core platform
apps/web
apps/api
packages/shared
repo: mobile-app # different release cycle
repo: data-pipeline # different team, different stack
repo: ml-models # heavy binary assets
Common Pitfalls#
- No build caching — monorepo CI without caching is painfully slow
- Circular dependencies — use lint rules and graph visualization to prevent them
- Giant PRs — atomic changes are powerful but reviewers need sane PR sizes
- Missing CODEOWNERS — without ownership, shared code rots
- Ignoring affected commands — running all tests on every PR wastes CI minutes
Quick-Start Checklist#
[ ] Pick a tool (Nx for full-featured, Turborepo for simplicity)
[ ] Set up workspace with apps/ and packages/ directories
[ ] Configure build caching (local first, then remote)
[ ] Add CODEOWNERS for every top-level directory
[ ] Set up affected-only CI builds
[ ] Document package creation and dependency conventions
[ ] Add dependency graph visualization to onboarding
Explore the full architecture series on Codelit.io. This is article #346 in our growing library of software architecture and system design guides.
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
GitHub Integration
Paste a repo URL and generate architecture from your actual codebase
Related articles
AI Agent Tool Use Architecture: Function Calling, ReAct Loops & Structured Outputs
6 min read
AI searchAI-Powered Search Architecture: Semantic Search, Hybrid Search, and RAG
8 min read
AI safetyAI Safety Guardrails Architecture: Input Validation, Output Filtering, and Human-in-the-Loop
8 min read
Try these templates
Netflix Video Streaming Architecture
Global video streaming platform with adaptive bitrate, CDN distribution, and recommendation engine.
10 componentsGitHub-like CI/CD Pipeline
Continuous integration and deployment system with parallel jobs, artifact caching, and environment management.
9 componentsSearch Engine Architecture
Web-scale search with crawling, indexing, ranking, and sub-second query serving.
8 componentsBuild this architecture
Generate an interactive Monorepo Architecture Guide in seconds.
Try it in Codelit →
Comments