Step 1
Monorepo vs polyrepo
2 views
Table of contents
As the number of projects grows, define boundaries and verification ownership before choosing folders. The repository model should fit team size, release independence, and permission boundaries.
1. Monorepo — many apps and services in one repository
workspace/
├── apps/
│ ├── web/
│ └── console/
├── services/
│ ├── api/
│ └── worker/
├── packages/
│ ├── contracts/
│ └── ui/
├── infra/
└── docs/
2. Benefits
- Atomic changes — verify a screen and its API contract together
- Shared code — keep types, contracts, and accessibility utilities in one place
- Consistent tooling — align package, container, and verification commands
- Refactor visibility — search the entire repository for impact
- Change traceability — inspect docs, schemas, and deployment settings together
3. Costs
- A larger checkout — more clone and indexing cost
- Complex CI — find changed apps and their dependency graph
- Harder permissions — protect sensitive areas in one repository
- Tooling constraints — shared runtime and package policies are required
4. Polyrepo — one repository per release unit
github.com/example/web
github.com/example/api
github.com/example/console
github.com/example/worker
5. Benefits
- Small repositories — faster clone and focused changes
- Permission separation — access control per repository
- Independent releases — a clear blast radius
- Tool autonomy — each team can choose its runtime and cadence
6. Costs
- Cross-repository changes — coordinate several PRs and versions
- Duplicated code — the same utility gets implemented repeatedly
- Version drift — TypeScript, Node, and contract versions diverge
- Lower discoverability — harder to find another team's implementation
7. Decision guide
| Situation | Prefer |
|---|---|
| Individual or small team | Monorepo |
| UI and API change together often | Monorepo |
| Independent releases and strict permissions | Polyrepo |
| Completely different languages and tooling | Polyrepo |
| Stable shared libraries | Polyrepo or an explicit packages boundary |
8. Practical monorepo tooling
Choose one of pnpm workspaces, Nx, Turborepo, or Lerna according to the team's caching and graph needs.
# pnpm-workspace.yaml
packages:
- "apps/*"
- "services/*"
- "packages/*"
pnpm --filter web add lodash
pnpm --filter web test
pnpm --filter "./services/*" typecheck
9. Change detection and CI
Building everything on every monorepo change makes feedback slow. Calculate changed paths and their dependencies; when a shared contract changes, widen consumer verification.
# ci.yml
filters:
web: apps/web/**
api: services/api/**
contracts: packages/contracts/**
steps:
- if: web or contracts
run: pnpm --filter web test
- if: api or contracts
run: pnpm --filter api test
10. Control coupling
Being in one repository does not mean importing each other's internals. Otherwise the result is a big ball of mud.
- Apps consume a service's public API and contract package only
- Shared code lives in
packages/with an explicit owner - Database models and migrations stay inside the service boundary
- Lint rules and CI fail on boundary violations
11. A practical checklist
- Split release units first; use role-based folder names.
- Document consumers of APIs, events, and database schemas.
- Give shared packages independent tests and version rules.
- Record the detected changes and actual verification list in CI logs.
- Keep contract, observability, and recovery standards consistent even across repositories.
Common traps
- Deploying only one side and breaking the contract
- Creating a workspace dependency loop
- Splitting into a hundred packages too early
- Defining permissions for sensitive data too late
Next
- 02-ssot-where