Monorepo Management
by @watermelon11
Master monorepo management with Turborepo, Nx, and pnpm workspaces to build efficient, scalable multi-package repositories with optimized builds and dependen...
clawhub install monorepo-managementπ About This 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
Core Concepts
1. Why Monorepos?
Advantages:
Challenges:
2. Monorepo Tools
Package Managers:
Build Systems:
Turborepo Setup
Initial Setup
# Create new monorepo
npx create-turbo@latest my-monorepo
cd my-monorepoStructure:
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
// 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": []
}
}
}
// 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": "pnpm@8.0.0"
}
Package Structure
// 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
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "tools/*"
// .npmrc
Hoist shared dependencies
shamefully-hoist=trueStrict peer dependencies
auto-install-peers=true
strict-peer-dependencies=truePerformance
store-dir=~/.pnpm-store
Dependency Management
# Install dependency in specific package
pnpm add react --filter @repo/ui
pnpm add -D typescript --filter @repo/uiInstall workspace dependency
pnpm add @repo/ui --filter webInstall in all packages
pnpm add -D eslint -wUpdate all dependencies
pnpm update -rRemove dependency
pnpm remove react --filter @repo/ui
Scripts
# Run script in specific package
pnpm --filter web dev
pnpm --filter @repo/ui buildRun in all packages
pnpm -r build
pnpm -r testRun in parallel
pnpm -r --parallel devFilter by pattern
pnpm --filter "@repo/*" build
pnpm --filter "...web" build # Build web and dependencies
Nx Monorepo
Setup
# Create Nx monorepo
npx create-nx-workspace@latest my-orgGenerate applications
nx generate @nx/react:app my-app
nx generate @nx/next:app my-next-appGenerate libraries
nx generate @nx/react:lib ui-components
nx generate @nx/js:lib utils
Configuration
// 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
# Run task for specific project
nx build my-app
nx test ui-components
nx lint utilsRun for affected projects
nx affected:build
nx affected:test --base=mainVisualize dependencies
nx graphRun in parallel
nx run-many --target=build --all --parallel=3
Shared Configurations
TypeScript Configuration
// 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
// 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
// packages/ui/src/button.tsx import * as React from 'react';} onClick={onClick} > {children} ); }export interface ButtonProps { variant?: 'primary' | 'secondary'; children: React.ReactNode; onClick?: () => void; }
export function Button({ variant = 'primary', children, onClick }: ButtonProps) { return (
// 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 ; }
Pattern 2: Shared Utilities
// 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
// 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
// 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
# Turborepo Remote Cache (Vercel)
npx turbo login
npx turbo linkCustom remote cache
turbo.json
{
"remoteCache": {
"signature": true,
"enabled": true
}
}
CI/CD for Monorepos
GitHub Actions
# .github/workflows/ci.yml
name: CIon:
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
# 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
Publishing Packages
# Using Changesets
pnpm add -Dw @changesets/cli
pnpm changeset initCreate changeset
pnpm changesetVersion packages
pnpm changeset versionPublish
pnpm changeset publish
# .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 }}
Resources
βοΈ Configuration
// 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": []
}
}
}
// 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": "pnpm@8.0.0"
}
Package Structure
// 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"
}
}
π Tips & 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