AgentSkillsCN

monorepo-patterns

采用现代工具构建Monorepo架构模式。当您搭建Turborepo、pnpm工作空间、Nx,或管理共享包时,可选用此技能。

SKILL.md
--- frontmatter
name: monorepo-patterns
description: Monorepo architecture patterns with modern tooling. Use when setting up Turborepo, pnpm workspaces, Nx, or managing shared packages.

Monorepo Patterns

Purpose

Guide architecture decisions for monorepo setups using modern tooling. Covers Turborepo, pnpm workspaces, Nx, shared packages, build caching, dependency management, and scaling strategies.

Activation

Use this skill when the user asks about:

  • Setting up a monorepo
  • Configuring workspaces (pnpm, npm, yarn)
  • Turborepo or Nx configuration
  • Sharing code between packages
  • Build caching and optimization
  • Dependency management across packages
  • CI/CD for monorepos

Project Structure

Recommended Layout

code
monorepo/
├── apps/
│   ├── web/                 # Next.js frontend
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   ├── api/                 # Express/Fastify backend
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   └── docs/                # Documentation site
│       ├── package.json
│       └── src/
├── packages/
│   ├── ui/                  # Shared React components
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   ├── config-eslint/       # Shared ESLint config
│   │   ├── package.json
│   │   └── index.js
│   ├── config-typescript/   # Shared TSConfig
│   │   ├── package.json
│   │   ├── base.json
│   │   ├── nextjs.json
│   │   └── node.json
│   ├── shared/              # Shared utilities and types
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   └── src/
│   └── database/            # Prisma schema and client
│       ├── package.json
│       ├── prisma/
│       └── src/
├── tooling/
│   ├── scripts/             # Shared build/deploy scripts
│   └── docker/              # Shared Dockerfiles
├── turbo.json
├── pnpm-workspace.yaml
├── package.json
├── .npmrc
└── tsconfig.json

pnpm Workspaces

pnpm-workspace.yaml

yaml
packages:
  - "apps/*"
  - "packages/*"
  - "tooling/*"

Root package.json

json
{
  "name": "monorepo",
  "private": true,
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "lint": "turbo lint",
    "test": "turbo test",
    "typecheck": "turbo typecheck",
    "clean": "turbo clean",
    "format": "prettier --write \"**/*.{ts,tsx,js,jsx,md,json}\""
  },
  "devDependencies": {
    "prettier": "^3.0.0",
    "turbo": "^2.0.0"
  },
  "packageManager": "pnpm@9.0.0",
  "engines": {
    "node": ">=20.0.0"
  }
}

.npmrc

ini
auto-install-peers=true
strict-peer-dependencies=false
link-workspace-packages=true

Internal Package References

In a consuming package's package.json:

json
{
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/shared": "workspace:*",
    "@repo/database": "workspace:*"
  }
}

Turborepo Configuration

turbo.json

json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [
    "**/.env.*local",
    "**/.env"
  ],
  "globalEnv": [
    "NODE_ENV",
    "CI"
  ],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json", "package.json"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
      "env": ["DATABASE_URL", "NEXT_PUBLIC_*"]
    },
    "dev": {
      "dependsOn": ["^build"],
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", ".eslintrc*", "eslint.config.*"]
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json"]
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tests/**", "vitest.config.*"],
      "outputs": ["coverage/**"]
    },
    "clean": {
      "cache": false
    }
  }
}

Key Concepts

  • ^build means "run build in all dependencies first" (topological ordering)
  • inputs define what files affect the cache hash
  • outputs define what gets cached
  • persistent marks long-running tasks like dev servers
  • cache: false disables caching for tasks with side effects

Filtering

bash
# Run only for a specific package
turbo build --filter=@repo/web

# Run for a package and its dependencies
turbo build --filter=@repo/web...

# Run for packages that changed since main
turbo build --filter=[main]

# Run for packages in apps/ directory
turbo build --filter=./apps/*

# Combine filters
turbo build --filter=@repo/web... --filter=!@repo/docs

Shared Package Patterns

Shared TypeScript Config

packages/config-typescript/base.json:

json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "incremental": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "exclude": ["node_modules", "dist"]
}

packages/config-typescript/nextjs.json:

json
{
  "extends": "./base.json",
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "ES2022"],
    "module": "ESNext",
    "jsx": "preserve",
    "plugins": [{ "name": "next" }],
    "noEmit": true
  }
}

Shared UI Package

packages/ui/package.json:

json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./button": {
      "types": "./dist/components/button.d.ts",
      "import": "./dist/components/button.js"
    }
  },
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch",
    "typecheck": "tsc --noEmit"
  },
  "devDependencies": {
    "@repo/config-typescript": "workspace:*",
    "tsup": "^8.0.0",
    "typescript": "^5.5.0"
  }
}

packages/ui/tsup.config.ts:

typescript
import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/index.ts", "src/components/*.tsx"],
  format: ["esm"],
  dts: true,
  splitting: true,
  clean: true,
  external: ["react", "react-dom"],
});

Shared Utilities Package

packages/shared/package.json:

json
{
  "name": "@repo/shared",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    },
    "./types": {
      "types": "./dist/types/index.d.ts",
      "import": "./dist/types/index.js"
    },
    "./utils": {
      "types": "./dist/utils/index.d.ts",
      "import": "./dist/utils/index.js"
    }
  },
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch"
  }
}

Build Caching

Remote Caching with Vercel

bash
# Login to Vercel remote cache
npx turbo login
npx turbo link

Or via environment variables in CI:

bash
TURBO_TOKEN=your_token
TURBO_TEAM=your_team

Custom Remote Cache

In turbo.json:

json
{
  "remoteCache": {
    "enabled": true,
    "signature": true
  }
}

Cache Debugging

bash
# Check what turbo will run (dry run)
turbo build --dry-run

# Show verbose output for cache hits/misses
turbo build --verbosity=2

# Force ignore cache
turbo build --force

# Show task graph
turbo build --graph

Dependency Management

Version Alignment

Use pnpm catalog feature or syncpack to keep versions aligned:

pnpm-workspace.yaml with catalog:

yaml
packages:
  - "apps/*"
  - "packages/*"

catalog:
  react: "^18.3.0"
  react-dom: "^18.3.0"
  typescript: "^5.5.0"
  vitest: "^2.0.0"
  zod: "^3.23.0"

Then in any package.json:

json
{
  "dependencies": {
    "react": "catalog:",
    "react-dom": "catalog:"
  }
}

Dependency Rules

  1. Shared dependencies go in root only if they are dev tools (prettier, turbo)
  2. Each package declares its own dependencies - never rely on hoisting
  3. Internal packages use workspace:* for linking
  4. Pin exact versions for critical dependencies in production apps
  5. Use peerDependencies for shared libraries that consumers must provide (React, etc.)

Scaling Patterns

When the Monorepo Grows

ScaleStrategy
< 10 packagesStandard Turborepo setup
10-50 packagesAdd remote caching, strict task inputs
50-200 packagesConsider Nx for fine-grained caching
200+ packagesCustom tooling, Bazel, or split into multiple repos

Code Ownership

Use CODEOWNERS to assign package owners:

code
# .github/CODEOWNERS
/apps/web/          @frontend-team
/apps/api/          @backend-team
/packages/ui/       @design-system-team
/packages/shared/   @platform-team
/packages/database/ @backend-team

Common Mistakes

MistakeImpactFix
No inputs in turbo.jsonCaching is ineffectiveDefine precise inputs per task
Importing from src/ of other packagesFragile, no build stepUse exports field and built output
Shared node_modules assumptionsWorks locally, fails in CIEach package declares its deps
No .npmrc configPhantom dependenciesSet strict-peer-dependencies
Giant shared packageEverything depends on everythingSplit into focused packages
No typecheck taskType errors caught lateAdd typecheck to pipeline