Turborepo
Build system and workspace manager for monorepos. Handles task orchestration, caching, and dependency resolution.
Workspace Commands
bash
pnpm dev # Run dev task in all packages pnpm build # Build all packages pnpm check # Lint + typecheck all packages pnpm test # Run tests in all packages # Run in specific package pnpm --filter @repo/api build pnpm --filter @repo/webapp dev pnpm --filter ./packages/ui build # Scope to changed packages (useful in CI) pnpm build --changed # Include dependencies pnpm build --include-dependencies
Key: Always run from repo root, never cd into packages.
Workspace Structure
Typical monorepo layout:
code
apps/
├── webapp/ # Main frontend app (@repo/webapp)
│ └── src/
│ ├── routes/ # TanStack Router
│ ├── components/ # UI components
│ └── entry.tsx # Entry point
│
packages/
├── api/ # Backend/API (@repo/api)
│ └── src/
│ └── routes/ # API endpoints
│
├── db/ # Database (@repo/db)
│ └── schema.ts # Drizzle schema
│
├── features/ # Feature components (@repo/features)
│ └── src/
│ └── islands/ # Island components
│
├── ui/ # Design system (@repo/ui)
│ └── src/
│ └── components/ # Primitives, patterns
│
├── shared/ # Shared utilities (@repo/shared)
│ └── src/
│ └── types/ # Shared types
│
└── config/
├── typescript-config/ # TypeScript configs
├── eslint-config/ # ESLint configs
└── tailwind-config/ # Tailwind configs
Customize: Adjust structure to your project's needs. Keep apps/ for user-facing products, packages/ for libraries.
<template id="package-json">json
{
"name": "@repo/package-name",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"check": "tsc && eslint src"
},
"dependencies": {},
"devDependencies": {},
"peerDependencies": {}
}
Conventions:
- •Use
@repo/scope for all internal packages - •Define scripts that turbo can orchestrate
- •No version numbers in package.json (monorepo symlinks via workspace:*)
Dependency Management
<template id="internal-dependencies">json
// package.json in apps/webapp
{
"dependencies": {
"@repo/api": "workspace:*",
"@repo/db": "workspace:*",
"@repo/features": "workspace:*",
"@repo/ui": "workspace:*"
}
}
Rules:
- •Use
workspace:*for internal dependencies (symlinks locally, resolves on publish) - •Never use relative paths (
../) for dependencies - •Never hardcode versions of internal packages
- •pnpm automatically handles workspace resolution
Turbo Configuration
<template id="turbo-config">json
// turbo.json (repo root)
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"dev": {
"cache": false,
"persistent": true
},
"build": {
"outputs": ["dist/**", "build/**"],
"cache": true
},
"check": {
"outputs": [],
"cache": true
},
"lint": {
"cache": true
},
"test": {
"outputs": ["coverage/**"],
"cache": false
}
},
"pipeline": {
// Task execution order
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["dev:init"]
},
"dev:init": {
"cache": false,
"persistent": true,
"outputs": []
}
}
}
Key concepts:
- •
cache: falsefor dev tasks (always run) - •
cache: truefor build/lint/test (skip if unchanged) - •
dependsOn: ["^build"]means "wait for dependencies to build first" - •
persistent: truekeeps task running (good for dev servers) - •
outputstells turbo what to cache and when to skip
Task Filtering
bash
# Filter by package name pnpm --filter @repo/api build # Filter by directory pnpm --filter ./packages/ui build # Filter by tag pnpm --filter "tag:frontend" build # Include dependencies of filtered package pnpm --filter @repo/webapp --include-dependencies build # Only changed packages (in CI) pnpm build --changed
Development Workflow
<template id="dev-workflow">bash
# Terminal 1: Start all dev servers pnpm dev # Watches: # - apps/webapp: Vite dev server on port 3000 # - apps/api: Backend dev server # - packages/ui: Storybook or dev build # Terminal 2: Type check and lint pnpm check # Runs: tsc + eslint in all packages # Terminal 3: Run tests (watching) pnpm test -- --watch # In another terminal, test specific package pnpm --filter @repo/api test -- --watch
Tip: Use task dependencies in turbo.json to run setup tasks before dev.
</template>Build Process
bash
# Build all packages (respects dependency order) pnpm build # Turborepo ensures: # 1. @repo/shared builds first (no dependencies) # 2. @repo/ui builds (depends on shared) # 3. @repo/features builds (depends on ui) # 4. @repo/api builds (depends on db, shared) # 5. apps/webapp builds (depends on all) # Build specific package and dependencies pnpm --filter @repo/api --include-dependencies build # Build only changed since last commit pnpm build --changed
Common Tasks
<template id="common-patterns">bash
# Setup: install all dependencies pnpm install # Build everything for production pnpm build # Type check and lint all code pnpm check # Run tests in all packages pnpm test # Add new dependency to package pnpm --filter @repo/api add express # Add dev dependency to package pnpm --filter @repo/api add -D @types/node # Remove dependency pnpm --filter @repo/api remove express # List workspace packages pnpm ls -r --depth 0
Key Rules
<instructions id="monorepo-rules">- •Always run commands from repo root (never
cdinto packages) - •Use
workspace:*for internal dependencies - •Keep
package.jsonnames consistent with@repo/scope - •Define meaningful scripts in each package's
package.json - •Use turbo.json to orchestrate task dependencies
- •Cache build/test/lint tasks, not dev tasks
- •Group related packages (apps/ vs packages/)
- •Document package purposes in README
Anti-Patterns
<anti-patterns id="monorepo-mistakes">- •Running commands from inside packages (
cd packages/api && pnpm build) - •Using relative paths for internal dependencies (
"@repo/ui": "../ui") - •Hardcoding versions of internal packages
- •Creating circular dependencies between packages
- •Forgetting to update turbo.json when adding new tasks
- •Not caching build outputs (wastes CI time)
- •Mixing app and library code in one package
- •Forgetting to configure
outputsin turbo.json - •Installing dev dependencies at workspace root (install in packages)