Understanding Monorepos
A monorepo is a version control strategy where multiple projects are stored in a single repository. This approach has gained popularity with large companies like Google, Facebook, and Microsoft, along with modern tools like Nx, Turborepo, and pnpm workspaces.
bash
# Monorepo structure
my-monorepo/
├── apps/
│ ├── web/ # Frontend application
│ ├── api/ # Backend API
│ └── mobile/ # Mobile app
├── packages/
│ ├── ui/ # Shared UI components
│ ├── utils/ # Shared utilities
│ └── types/ # Shared TypeScript types
├── package.json
└── turbo.json # Build orchestrationMonorepo vs Polyrepo
Key Differences
bash
# Monorepo
✓ Single source of truth
✓ Unified CI/CD
✓ Shared dependencies
✓ Atomic commits
✓ Easy code sharing
✗ Larger repository size
✗ Slower git operations
✗ Complex build setup
# Polyrepo
✓ Independent repositories
✓ Faster git operations
✓ Separate deployments
✗ Code duplication
✗ Version synchronization
✗ Complex dependenciesMonorepo Tools
pnpm Workspaces
json
// pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint"
},
"devDependencies": {
"turbo": "^1.10.0",
"typescript": "^5.0.0"
}
}
// apps/web/package.json
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}Nx Monorepo
bash
# Create Nx workspace
npx create-nx-workspace@latest my-workspace
# Choose stack:
# - Integrated (monorepo with Nx)
# - Standalone (single project)
# Add applications
nx g @nx/react:app my-app
# Add libraries
nx g @nx/react:lib shared-ui
# Structure:
my-workspace/
├── apps/
│ └── my-app/
├── libs/
│ └── shared-ui/
├── nx.json
└── package.jsonTurborepo
json
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
}
}
}
// Run tasks
turbo run build # Build all apps
turbo run dev # Start dev servers
turbo run test # Run testsProject Structure
Recommended Layout
bash
my-monorepo/
├── apps/ # Deployable applications
│ ├── web/ # React app
│ ├── api/ # Express server
│ └── admin/ # Admin dashboard
├── packages/ # Shared packages
│ ├── ui/ # UI components
│ ├── utils/ # Utilities
│ ├── config/ # Shared config
│ └── types/ # TypeScript types
├── tools/ # Build tools
│ ├── eslint-config/
│ └── tsconfig/
├── .github/ # CI/CD
├── docs/ # Documentation
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── nx.jsonPackage Naming
json
// Use scoped names for organization
{
"name": "@myorg/web",
"version": "1.0.0"
}
// Reference in dependencies
{
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
// Publish to npm
{
"name": "@myorg/ui",
"publishConfig": {
"access": "public"
}
}Dependency Management
Internal Dependencies
json
// packages/ui/package.json
{
"name": "@myorg/ui",
"dependencies": {
"@myorg/utils": "workspace:*",
"@myorg/types": "workspace:*"
}
}
// apps/web/package.json
{
"name": "@myorg/web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
// pnpm installs and links automatically
pnpm installExternal Dependencies
json
// Use package managers' deduplication
{
"dependencies": {
"react": "^18.0.0"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
// pnpm deduplicates across workspace
// Single copy of react, typescript, etc.
pnpm install --shamefully-hoist # If neededBuild Orchestration
Task Dependencies
json
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // Build dependencies first
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"], // After build
"outputs": ["coverage/**"]
},
"dev": {
"cache": false, // Don't cache dev mode
"persistent": true // Long-running process
}
}
}
// Build order automatically determined
// packages/utils → packages/ui → apps/webCaching Strategy
bash
# Turborepo caching
turbo run build
# First run: Execute all builds
# Second run: Skip unchanged packages
# Cache stored in .turbo/cache
# Remote caching (Vercel/Team City)
npx turbo login
npx turbo link
# CI/CD with cache
- run: pnpm install
- run: pnpm turbo run build --force=falseCI/CD Integration
GitHub Actions
yaml
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run lint
run: pnpm turbo run lint
- name: Run typecheck
run: pnpm turbo run typecheck
- name: Run tests
run: pnpm turbo run test
- name: Build
run: pnpm turbo run build
- name: Cache Turborepo
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-Nx CI
yaml
# .github/workflows/ci.yml
- uses: nrwl/nx-set-shas@v3
with:
main-branch-name: 'main'
- run: pnpm nx affected -t build test --parallel
env:
NX_BRANCH: ${{ github.event.pull_request.head.sha || github.sha }}
NX_BASE: ${{ steps.set-shas.outputs.base }}Versioning Strategy
Independent Versioning
bash
# Changesets for versioning
pnpm add -D @changesets/cli
pnpm changeset init
# Create changeset for changes
pnpm changeset
# Select packages and version bump
# @myorg/ui: minor
# @myorg/utils: patch
# Update versions
pnpm changeset version
# Publish packages
pnpm changeset publishFixed Versioning
bash
# Lerna for fixed versioning
pnpm add -D lerna
# lerna.json
{
"version": "1.0.0",
"packages": ["packages/*"],
"npmClient": "pnpm"
}
# Bump all packages
pnpm lerna version patch
# Publish all packages
pnpm lerna publish from-gitBest Practices
1. Separate Apps from Libraries
bash
# apps/ - Deployable applications
# packages/ - Shared libraries
# Clear distinction between:
# - What users interact with (apps)
# - What developers use (packages)2. Use Workspace Aliases
json
{
"dependencies": {
"@myorg/ui": "workspace:*" // Always latest
}
}
// For publishing, use:
"publishConfig": {
"access": "public"
}3. Optimize Build Performance
json
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": ["src/**/*.tsx", "src/**/*.ts"]
}
}
}
// Only rebuild when inputs change
// Cache outputs4. Enforce Code Standards
json
// .eslintrc.js
module.exports = {
extends: ["@myorg/eslint-config"],
ignorePatterns: ["node_modules/", "dist/"]
};
// Run across all packages
pnpm turbo run lintCommon Patterns
Microfrontend Monorepo
bash
my-monorepo/
├── apps/
│ ├── shell/ # Host application
│ ├── feature-auth/ # Auth feature
│ └── feature-dashboard/ # Dashboard feature
├── packages/
│ ├── shared-ui/ # Shared components
│ └── config/ # Module federation configFull-Stack Monorepo
bash
my-monorepo/
├── apps/
│ ├── web/ # Next.js frontend
│ └── api/ # tRPC backend
├── packages/
│ ├── ui/ # React components
│ ├── db/ # Database client
│ └── types/ # Shared typesDesign System Monorepo
bash
my-monorepo/
├── apps/
│ └── docs/ # Storybook docs
├── packages/
│ ├── components/ # React components
│ ├── icons/ # Icon library
│ └── theme/ # Theme configurationMigration Strategy
Gradual Migration
bash
# Phase 1: Create monorepo
mkdir my-monorepo && cd my-monorepo
pnpm init
# Phase 2: Move one project
mkdir apps/web
# Copy existing project
# Phase 3: Extract shared code
mkdir packages/utils
# Move common utilities
# Phase 4: Add more projects
mkdir apps/api
# Move API projectTroubleshooting
Dependency Conflicts
bash
# Problem: Version conflicts
pnpm install
# error: peer dependency conflicts
# Solution: Use overrides
{
"pnpm": {
"overrides": {
"react": "18.2.0"
}
}
}Build Order Issues
bash
# Problem: Wrong build order
turbo run build
# Solution: Check dependencies
{
"build": {
"dependsOn": ["^build"] // Build dependencies first
}
}Conclusion
Monorepos provide a powerful way to manage multiple related projects while maintaining code sharing and unified development practices. With modern tools like Turborepo, Nx, and pnpm workspaces, monorepos are more accessible than ever.
Key takeaways:
- Use monorepos for related projects
- Choose right tool (Nx/Turborepo/pnpm)
- Optimize build with caching
- Version packages independently
- Integrate with CI/CD
Build better with monorepos!