10. Monorepo Management
Learning Objectives
- Understand monorepo concepts and pros/cons
- Build optimization with Nx and Turborepo
- Dependency management and code sharing strategies
- Performance optimization for large-scale monorepos
Table of Contents
- Monorepo Overview
- Monorepo Tools
- Using Nx
- Using Turborepo
- Dependency Management
- CI/CD Optimization
- Practice Exercises
1. Monorepo Overview
1.1 Monorepo vs Polyrepo
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Polyrepo (Polyrepo) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β frontend β β backend β β shared-lib β β
β β (repo) β β (repo) β β (repo) β β
β ββββββββββββββ€ ββββββββββββββ€ ββββββββββββββ€ β
β β .git/ β β .git/ β β .git/ β β
β β package.jsonβ β package.jsonβ β package.jsonβ β
β β src/ β β src/ β β src/ β β
β ββββββββββββββ ββββββββββββββ ββββββββββββββ β
β β
β Features: β
β β’ Independent version control β
β β’ Share packages via npm/pypi β
β β’ Easy per-project permission management β
β β’ Risk of dependency version mismatches β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Monorepo (Monorepo) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β my-company (single repo) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ β
β β .git/ β β
β β package.json (root) β β
β β nx.json / turbo.json β β
β β β β
β β packages/ β β
β β βββ frontend/ β β
β β β βββ package.json β β
β β β βββ src/ β β
β β βββ backend/ β β
β β β βββ package.json β β
β β β βββ src/ β β
β β βββ shared-lib/ β β
β β βββ package.json β β
β β βββ src/ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β Features: β
β β’ All code in single repository β
β β’ Atomic commits (modify multiple packages at once) β
β β’ Easy code reuse β
β β’ Consistent tools and settings β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1.2 Monorepo Pros and Cons
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Pros β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β Easy code sharing β
β - Shared libraries immediately available β
β - No version mismatch issues β
β β
β β Atomic changes β
β - Changes across multiple packages in single commit β
β - API changes and client updates simultaneously β
β β
β β Consistency β
β - Same lint, test, build configuration β
β - Same dependency versions β
β β
β β Easy refactoring β
β - Search/modify across entire codebase β
β - IDE support (autocomplete, find references) β
β β
β β Team collaboration β
β - Easy reference to other teams' code β
β - Understand full context in code reviews β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Cons β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β Repository size β
β - Increased clone time β
β - Complex CI caching β
β β
β β Build time β
β - Full builds take long β
β - Optimization needed to build only affected parts β
β β
β β Tool complexity β
β - Dedicated tools needed (Nx, Turborepo, Bazel) β
β - Learning curve β
β β
β β Permission management β
β - Difficult per-code access control β
β - Partially solved with CODEOWNERS β
β β
β β Dependency conflicts β
β - Issues when different versions needed β
β - Hoisting issues β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1.3 Monorepo Use Cases
Major monorepo examples:
β’ Google - billions of lines of code, single repository
β’ Facebook - React, Jest, etc.
β’ Microsoft - managed with Rush.js
β’ Uber - Go monorepo
β’ Airbnb - JavaScript monorepo
Good fit for:
β’ Multiple apps sharing common code
β’ Microservices + shared libraries
β’ Same team managing multiple packages
β’ API and client need synchronization
Not suitable for:
β’ Completely independent projects
β’ Different languages/tech stacks
β’ Strict access control needed
β’ Open source with many external contributors
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Monorepo Tool Comparison β
ββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββ€
β β Nx β Turborepo β Lerna β
ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌβββββββββββββββββββ€
β Developer β Nrwl β Vercel β (maintenance) β
β Language β JS/TS (+more)β JS/TS β JS/TS β
β Build cachingβ β β β β β β
β Remote cache β Nx Cloud βVercel/custom β β β
β Dep graph β β β β β β³ β
β Code gen β β β β β β β
β Plugins β Many β Few β Few β
β Config β High β Low β Low β
β Scale fitnessβ Excellent β Good β Moderate β
ββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββ
2.2 Basic Structure (npm/yarn/pnpm workspaces)
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
],
"scripts": {
"build": "npm run build --workspaces",
"test": "npm run test --workspaces",
"lint": "npm run lint --workspaces"
},
"devDependencies": {
"typescript": "^5.0.0",
"eslint": "^8.0.0"
}
}
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
my-monorepo/
βββ package.json
βββ pnpm-workspace.yaml
βββ packages/
β βββ ui/
β β βββ package.json
β β βββ src/
β βββ utils/
β βββ package.json
β βββ src/
βββ apps/
βββ web/
β βββ package.json
β βββ src/
βββ api/
βββ package.json
βββ src/
3. Using Nx
3.1 Creating Nx Project
# Create new Nx workspace
npx create-nx-workspace@latest my-workspace
# Option selection:
# - Integrated monorepo (integrated)
# - Package-based monorepo (package-based)
# - Standalone app (standalone)
# Add Nx to existing repository
npx nx@latest init
3.2 Nx Structure
my-workspace/
βββ nx.json # Nx configuration
βββ workspace.json # (optional) Project configuration
βββ package.json
βββ tsconfig.base.json # Shared TypeScript config
βββ apps/ # Applications
β βββ web/
β β βββ project.json # Per-project config
β β βββ src/
β β βββ tsconfig.json
β βββ api/
β βββ project.json
β βββ src/
β βββ tsconfig.json
βββ libs/ # Libraries
β βββ shared/
β β βββ ui/
β β β βββ project.json
β β β βββ src/
β β βββ utils/
β β βββ project.json
β β βββ src/
β βββ feature/
β βββ auth/
β βββ project.json
β βββ src/
βββ tools/ # Custom tools
3.3 nx.json Configuration
// nx.json
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.ts"
],
"sharedGlobals": ["{workspaceRoot}/tsconfig.base.json"]
},
"plugins": [
{
"plugin": "@nx/vite/plugin",
"options": {
"buildTargetName": "build",
"serveTargetName": "serve"
}
}
],
"defaultBase": "main"
}
3.4 Nx Commands
# Visualize project graph
nx graph
# Build specific project
nx build web
# Build only affected projects
nx affected:build --base=main
# Test affected projects
nx affected:test --base=main
# Parallel execution
nx run-many --target=build --parallel=5
# Run all projects
nx run-many --target=build --all
# Specific projects only
nx run-many --target=test --projects=web,api
# Check cache status
nx show project web
# Code generation
nx generate @nx/react:component button --project=ui
nx generate @nx/node:application api
nx generate @nx/js:library utils
# Migration
nx migrate latest
nx migrate --run-migrations
3.5 Nx Cloud (Remote Caching)
# Connect to Nx Cloud
npx nx connect-to-nx-cloud
# Or configure manually
# Add to nx.json:
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"accessToken": "your-access-token",
"cacheableOperations": ["build", "test", "lint"]
}
}
}
}
4. Using Turborepo
4.1 Turborepo Setup
# Create new project
npx create-turbo@latest
# Add to existing monorepo
npm install turbo --save-dev
4.2 turbo.json Configuration
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build", "test", "lint"]
}
}
}
4.3 Turborepo Commands
# Build all packages
turbo build
# Specific package only
turbo build --filter=web
# Include dependencies
turbo build --filter=web...
# Include dependents
turbo build --filter=...shared-ui
# Changed packages only
turbo build --filter=[HEAD^1]
# Limit parallel execution
turbo build --concurrency=10
# Visualize graph
turbo build --graph
# Run without cache
turbo build --force
# Dry run
turbo build --dry-run
4.4 Remote Caching (Vercel)
# Connect to Vercel
npx turbo login
npx turbo link
# Or use environment variables
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
// Add to turbo.json
{
"remoteCache": {
"signature": true
}
}
4.5 Turborepo Project Structure
my-turborepo/
βββ turbo.json
βββ package.json
βββ apps/
β βββ web/
β β βββ package.json
β β βββ next.config.js
β β βββ src/
β βββ docs/
β βββ package.json
β βββ src/
βββ packages/
βββ ui/
β βββ package.json
β βββ src/
βββ config/
β βββ eslint/
β β βββ package.json
β βββ typescript/
β βββ package.json
βββ utils/
βββ package.json
βββ src/
5. Dependency Management
5.1 Internal Package References
// packages/ui/package.json
{
"name": "@myorg/ui",
"version": "0.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./button": {
"import": "./dist/button.mjs",
"require": "./dist/button.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts"
}
}
// apps/web/package.json
{
"name": "web",
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
5.2 TypeScript Path Configuration
// tsconfig.base.json (root)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@myorg/ui": ["packages/ui/src/index.ts"],
"@myorg/ui/*": ["packages/ui/src/*"],
"@myorg/utils": ["packages/utils/src/index.ts"],
"@myorg/utils/*": ["packages/utils/src/*"]
}
}
}
// apps/web/tsconfig.json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"],
"references": [
{ "path": "../../packages/ui" },
{ "path": "../../packages/utils" }
]
}
5.3 Version Management
# Use Changesets (version management)
npm install @changesets/cli -D
npx changeset init
# Add changeset
npx changeset
# Select version bump type: major/minor/patch
# Select affected packages
# Write change description
# Update versions
npx changeset version
# Publish
npx changeset publish
// .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [["@myorg/ui", "@myorg/utils"]],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
5.4 Shared Configuration
// packages/config/eslint/index.js
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
// Common rules
}
};
// apps/web/.eslintrc.js
module.exports = {
root: true,
extends: ['@myorg/eslint-config'],
// Additional project-specific settings
};
6. CI/CD Optimization
6.1 GitHub Actions Configuration
# .github/workflows/ci.yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history (needed for affected calculation)
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
# When using Nx
- name: Nx affected
run: |
npx nx affected:lint --base=origin/main
npx nx affected:test --base=origin/main
npx nx affected:build --base=origin/main
# When using Turborepo
# - name: Turbo build
# run: pnpm turbo build --filter=[origin/main...]
# Remote caching (Nx Cloud)
nx-cloud:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Build with Nx Cloud
run: npx nx affected:build --base=origin/main
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
6.2 Affected Scope Analysis
# Nx - list affected projects
nx show projects --affected --base=main
# Affected files list
nx show projects --affected --base=main --files
# Turborepo - changed packages only
turbo build --filter=[HEAD^1]
# Check changed files with Git
git diff --name-only HEAD^1
# Path-based filtering (GitHub Actions)
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
web:
- 'apps/web/**'
api:
- 'apps/api/**'
shared:
- 'packages/**'
- if: steps.changes.outputs.web == 'true'
run: pnpm build --filter=web
6.3 Caching Strategy
# GitHub Actions caching
- name: Cache turbo build
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
- name: Cache node_modules
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-modules-${{ hashFiles('**/pnpm-lock.yaml') }}
6.4 Selective Deployment
# Deploy only changed apps
name: Deploy
on:
push:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
web: ${{ steps.filter.outputs.web }}
api: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
web:
- 'apps/web/**'
- 'packages/**'
api:
- 'apps/api/**'
- 'packages/**'
deploy-web:
needs: changes
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying web..."
deploy-api:
needs: changes
if: needs.changes.outputs.api == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "Deploying api..."
7. Practice Exercises
Exercise 1: Initial Monorepo Setup
# Requirements:
# 1. Set up monorepo with pnpm workspaces
# 2. Create shared UI library
# 3. Use UI library in web app
# 4. Configure TypeScript paths
# Write structure and configuration files:
Exercise 2: Nx Workspace
# Requirements:
# 1. Create Nx workspace
# 2. Add React app
# 3. Add Node API
# 4. Create shared library
# 5. Check dependency graph
# Write commands:
Exercise 3: Turborepo Pipeline
// Requirements:
// 1. Define build, test, lint, deploy tasks
// 2. Configure appropriate dependencies
// 3. Configure caching
// 4. Configure dev server
// Write turbo.json:
Exercise 4: CI/CD Optimization
# Requirements:
# 1. Build/test only affected projects
# 2. Configure remote caching
# 3. Deploy only changed apps
# 4. Reduce build time with caching
# Write GitHub Actions workflow:
Next Steps
References
β Previous: Advanced Git Techniques | Table of Contents