Claude Agent Skill · by Wshobson

Monorepo Management

This is a comprehensive guide for setting up and managing monorepos using Turborepo, Nx, and pnpm workspaces. It covers the practical stuff you'll actually need

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill monorepo-management
Works with Paperclip

How Monorepo Management fits into a Paperclip company.

Monorepo Management drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md614 lines
Expand
---name: monorepo-managementdescription: 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 monoreponpx create-turbo@latest my-monorepocd 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": "pnpm@8.0.0"}``` ### 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.yamlpackages:  - "apps/*"  - "packages/*"  - "tools/*"``` ```json// .npmrc# Hoist shared dependenciesshamefully-hoist=true # Strict peer dependenciesauto-install-peers=truestrict-peer-dependencies=true # Performancestore-dir=~/.pnpm-store``` ### Dependency Management ```bash# Install dependency in specific packagepnpm add react --filter @repo/uipnpm add -D typescript --filter @repo/ui # Install workspace dependencypnpm add @repo/ui --filter web # Install in all packagespnpm add -D eslint -w # Update all dependenciespnpm update -r # Remove dependencypnpm remove react --filter @repo/ui``` ### Scripts ```bash# Run script in specific packagepnpm --filter web devpnpm --filter @repo/ui build # Run in all packagespnpm -r buildpnpm -r test # Run in parallelpnpm -r --parallel dev # Filter by patternpnpm --filter "@repo/*" buildpnpm --filter "...web" build  # Build web and dependencies``` ## Nx Monorepo ### Setup ```bash# Create Nx monoreponpx create-nx-workspace@latest my-org # Generate applicationsnx generate @nx/react:app my-appnx generate @nx/next:app my-next-app # Generate librariesnx generate @nx/react:lib ui-componentsnx 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 projectnx build my-appnx test ui-componentsnx lint utils # Run for affected projectsnx affected:buildnx affected:test --base=main # Visualize dependenciesnx graph # Run in parallelnx 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.jsmodule.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.jsmodule.exports = {  extends: ["@repo/config/eslint-preset"],  rules: {    // App-specific rules  },};``` ## Code Sharing Patterns ### Pattern 1: Shared UI Components ```typescript// packages/ui/src/button.tsximport * 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.tsexport { Button, type ButtonProps } from './button';export { Input, type InputProps } from './input'; // apps/web/src/app.tsximport { Button } from '@repo/ui'; export function App() {  return <Button variant="primary">Click me</Button>;}``` ### Pattern 2: Shared Utilities ```typescript// packages/utils/src/string.tsexport 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.tsexport * from "./string";export * from "./array";export * from "./date"; // Usage in appsimport { capitalize, truncate } from "@repo/utils";``` ### Pattern 3: Shared Types ```typescript// packages/types/src/user.tsexport 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 backendimport 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 loginnpx turbo link # Custom remote cache# turbo.json{  "remoteCache": {    "signature": true,    "enabled": true  }}``` ## CI/CD for Monorepos ### GitHub Actions ```yaml# .github/workflows/ci.ymlname: 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 workspace2. **Shared Configs**: Centralize ESLint, TypeScript, Prettier configs3. **Dependency Graph**: Keep it acyclic, avoid circular dependencies4. **Cache Effectively**: Configure inputs/outputs correctly5. **Type Safety**: Share types between frontend/backend6. **Testing Strategy**: Unit tests in packages, E2E in apps7. **Documentation**: README in each package8. **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 Changesetspnpm add -Dw @changesets/clipnpm changeset init # Create changesetpnpm changeset # Version packagespnpm changeset version # Publishpnpm 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 }}```