Skills › Software Development › Dev workflow & Git
monorepo-management
Master monorepo management with Turborepo, Nx, and pnpm workspaces to build efficient, scalable multi-package repositories with optimized builds and dependency management. Use when setting up monorepos, optimizing builds, or managing shared dependencies.
Tools: react,-D,ui,–frozen-lockfile,-Dw,type
The full skill
—
name: monorepo-management
description: Master monorepo management with Turborepo, Nx, and pnpm workspaces to build efficient, scalable multi-package repositories with optimized builds and dependency management. Use when setting up monorepos, optimizing builds, or managing shared dependencies.
—
# Monorepo Management
Build efficient, scalable monorepos that enable code sharing, consistent tooling, and atomic changes across multiple packages and applications.
## When to Use This Skill
– Setting up new monorepo projects
– Migrating from multi-repo to monorepo
– Optimizing build and test performance
– Managing shared dependencies
– Implementing code sharing strategies
– Setting up CI/CD for monorepos
– Versioning and publishing packages
– Debugging monorepo-specific issues
## Core Concepts
### 1. Why Monorepos?
**Advantages:**
– Shared code and dependencies
– Atomic commits across projects
– Consistent tooling and standards
– Easier refactoring
– Simplified dependency management
– Better code visibility
**Challenges:**
– Build performance at scale
– CI/CD complexity
– Access control
– Large Git repository
### 2. Monorepo Tools
**Package Managers:**
– pnpm workspaces (recommended)
– npm workspaces
– Yarn workspaces
**Build Systems:**
– Turborepo (recommended for most)
– Nx (feature-rich, complex)
– Lerna (older, maintenance mode)
## Turborepo Setup
### Initial Setup
“`bash
# Create new monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo
# Structure:
# apps/
# web/ – Next.js app
# docs/ – Documentation site
# packages/
# ui/ – Shared UI components
# config/ – Shared configurations
# tsconfig/ – Shared TypeScript configs
# turbo.json – Turborepo configuration
# package.json – Root package.json
“`
### Configuration
“`json
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
“`
“`json
// package.json (root)
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint",
"format": "prettier –write \"**/*.{ts,tsx,md}\"",
"clean": "turbo run clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "^1.10.0",
"prettier": "^3.0.0",
"typescript": "^5.0.0"
},
"packageManager": "[email protected]"
}
“`
### Package Structure
“`json
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./button": {
"import": "./dist/button.js",
"types": "./dist/button.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts –format esm,cjs –dts",
"dev": "tsup src/index.ts –format esm,cjs –dts –watch",
"lint": "eslint src/",
"type-check": "tsc –noEmit"
},
"devDependencies": {
"@repo/tsconfig": "workspace:*",
"tsup": "^7.0.0",
"typescript": "^5.0.0"
},
"dependencies": {
"react": "^18.2.0"
}
}
“`
## pnpm Workspaces
### Setup
“`yaml
# pnpm-workspace.yaml
packages:
– "apps/*"
– "packages/*"
– "tools/*"
“`
“`json
// .npmrc
# Hoist shared dependencies
shamefully-hoist=true
# Strict peer dependencies
auto-install-peers=true
strict-peer-dependencies=true
# Performance
store-dir=~/.pnpm-store
“`
### Dependency Management
“`bash
# Install dependency in specific package
pnpm add react –filter @repo/ui
pnpm add -D typescript –filter @repo/ui
# Install workspace dependency
pnpm add @repo/ui –filter web
# Install in all packages
pnpm add -D eslint -w
# Update all dependencies
pnpm update -r
# Remove dependency
pnpm remove react –filter @repo/ui
“`
### Scripts
“`bash
# Run script in specific package
pnpm –filter web dev
pnpm –filter @repo/ui build
# Run in all packages
pnpm -r build
pnpm -r test
# Run in parallel
pnpm -r –parallel dev
# Filter by pattern
pnpm –filter "@repo/*" build
pnpm –filter "…web" build # Build web and dependencies
“`
## Nx Monorepo
### Setup
“`bash
# Create Nx monorepo
npx create-nx-workspace@latest my-org
# Generate applications
nx generate @nx/react:app my-app
nx generate @nx/next:app my-next-app
# Generate libraries
nx generate @nx/react:lib ui-components
nx generate @nx/js:lib utils
“`
### Configuration
“`json
// nx.json
{
"extends": "nx/presets/npm.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|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json"
],
"sharedGlobals": []
}
}
“`
### Running Tasks
“`bash
# Run task for specific project
nx build my-app
nx test ui-components
nx lint utils
# Run for affected projects
nx affected:build
nx affected:test –base=main
# Visualize dependencies
nx graph
# Run in parallel
nx run-many –target=build –all –parallel=3
“`
## Shared Configurations
### TypeScript Configuration
“`json
// packages/tsconfig/base.json
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"declaration": true
},
"exclude": ["node_modules"]
}
// packages/tsconfig/react.json
{
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
// apps/web/tsconfig.json
{
"extends": "@repo/tsconfig/react.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
“`
### ESLint Configuration
“`javascript
// packages/config/eslint-preset.js
module.exports = {
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier",
],
plugins: ["@typescript-eslint", "react", "react-hooks"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2022,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: "detect",
},
},
rules: {
"@typescript-eslint/no-unused-vars": "error",
"react/react-in-jsx-scope": "off",
},
};
// apps/web/.eslintrc.js
module.exports = {
extends: ["@repo/config/eslint-preset"],
rules: {
// App-specific rules
},
};
“`
## Code Sharing Patterns
### Pattern 1: Shared UI Components
“`typescript
// packages/ui/src/button.tsx
import * as React from 'react';
export interface ButtonProps {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
}
export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{children}
</button>
);
}
// packages/ui/src/index.ts
export { Button, type ButtonProps } from './button';
export { Input, type InputProps } from './input';
// apps/web/src/app.tsx
import { Button } from '@repo/ui';
export function App() {
return <Button variant="primary">Click me</Button>;
}
“`
### Pattern 2: Shared Utilities
“`typescript
// packages/utils/src/string.ts
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str: string, length: number): string {
return str.length > length ? str.slice(0, length) + "…" : str;
}
// packages/utils/src/index.ts
export * from "./string";
export * from "./array";
export * from "./date";
// Usage in apps
import { capitalize, truncate } from "@repo/utils";
“`
### Pattern 3: Shared Types
“`typescript
// packages/types/src/user.ts
export interface User {
id: string;
email: string;
name: string;
role: "admin" | "user";
}
export interface CreateUserInput {
email: string;
name: string;
password: string;
}
// Used in both frontend and backend
import type { User, CreateUserInput } from "@repo/types";
“`
## Build Optimization
### Turborepo Caching
“`json
// turbo.json
{
"pipeline": {
"build": {
// Build depends on dependencies being built first
"dependsOn": ["^build"],
// Cache these outputs
"outputs": ["dist/**", ".next/**"],
// Cache based on these inputs (default: all files)
"inputs": ["src/**/*.tsx", "src/**/*.ts", "package.json"]
},
"test": {
// Run tests in parallel, don't depend on build
"cache": true,
"outputs": ["coverage/**"]
}
}
}
“`
### Remote Caching
“`bash
# Turborepo Remote Cache (Vercel)
npx turbo login
npx turbo link
# Custom remote cache
# turbo.json
{
"remoteCache": {
"signature": true,
"enabled": true
}
}
“`
## CI/CD for Monorepos
### GitHub Actions
“`yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v3
with:
fetch-depth: 0 # For Nx affected commands
– 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: Build
run: pnpm turbo run build
– name: Test
run: pnpm turbo run test
– name: Lint
run: pnpm turbo run lint
– name: Type check
run: pnpm turbo run type-check
“`
### Deploy Affected Only
“`yaml
# Deploy only changed apps
– name: Deploy affected apps
run: |
if pnpm nx affected:apps –base=origin/main –head=HEAD | grep -q "web"; then
echo "Deploying web app"
pnpm –filter web deploy
fi
“`
## Best Practices
1. **Consistent Versioning**: Lock dependency versions across workspace
2. **Shared Configs**: Centralize ESLint, TypeScript, Prettier configs
3. **Dependency Graph**: Keep it acyclic, avoid circular dependencies
4. **Cache Effectively**: Configure inputs/outputs correctly
5. **Type Safety**: Share types between frontend/backend
6. **Testing Strategy**: Unit tests in packages, E2E in apps
7. **Documentation**: README in each package
8. **Release Strategy**: Use changesets for versioning
## Common Pitfalls
– **Circular Dependencies**: A depends on B, B depends on A
– **Phantom Dependencies**: Using deps not in package.json
– **Incorrect Cache Inputs**: Missing files in Turborepo inputs
– **Over-Sharing**: Sharing code that should be separate
– **Under-Sharing**: Duplicating code across packages
– **Large Monorepos**: Without proper tooling, builds slow down
## Publishing Packages
“`bash
# Using Changesets
pnpm add -Dw @changesets/cli
pnpm changeset init
# Create changeset
pnpm changeset
# Version packages
pnpm changeset version
# Publish
pnpm changeset publish
“`
“`yaml
# .github/workflows/release.yml
– name: Create Release Pull Request or Publish
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
“`