turborepo-monorepo
Guide for managing TypeScript monorepos with Turborepo and pnpm workspaces. Covers package creation, dependencies, pipeline configuration, and common patterns.
When & Why to Use This Skill
This Claude skill provides a comprehensive technical framework for architecting and managing high-performance TypeScript monorepos using Turborepo and pnpm workspaces. It streamlines the complex process of setting up build pipelines, managing internal dependencies, and implementing shared configurations, enabling developers to maintain scalable codebases with optimized build speeds and consistent standards.
Use Cases
- Architecting a multi-package TypeScript project to share core logic, types, and UI components across web, mobile, and server applications.
- Optimizing CI/CD performance by configuring Turborepo's task orchestration and caching to reduce redundant build and test cycles.
- Managing complex dependency graphs in large-scale repositories using pnpm workspaces to ensure version consistency and efficient disk usage.
- Standardizing development environments by implementing shared ESLint, Prettier, and TypeScript configurations across all internal packages.
- Streamlining the onboarding process for new developers by providing clear patterns for package creation and internal module exports.
| name | turborepo-monorepo |
|---|---|
| description | "Guide for managing TypeScript monorepos with Turborepo and pnpm workspaces. Covers package creation, dependencies, pipeline configuration, and common patterns." |
Turborepo + pnpm Monorepo Management
Overview
Turborepo orchestrates builds across packages in a monorepo. pnpm workspaces manage dependencies. Together they enable efficient multi-package TypeScript projects.
Project Structure
project/
├── package.json # Root package.json (workspaces config)
├── pnpm-workspace.yaml # pnpm workspace definition
├── turbo.json # Turborepo pipeline configuration
├── packages/
│ ├── types/ # Shared types
│ ├── core/ # Core logic
│ └── client/ # Client library
└── apps/
├── server/ # Server application
└── cli/ # CLI application
Root Configuration
package.json
{
"name": "my-monorepo",
"private": true,
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"test": "turbo test",
"check-types": "turbo check-types"
},
"devDependencies": {
"turbo": "^2.0.0",
"typescript": "^5.0.0"
}
}
pnpm-workspace.yaml
packages:
- "packages/*"
- "apps/*"
turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
},
"check-types": {
"dependsOn": ["^build"]
}
}
}
Key concepts:
^buildmeans "build dependencies first"outputsdefines what to cachepersistent: truefor long-running dev serverscache: falsefor tasks that shouldn't be cached
Package Configuration
Package package.json
{
"name": "@repo/core",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./storage": {
"types": "./dist/storage/index.d.ts",
"import": "./dist/storage/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint src/",
"test": "vitest run",
"check-types": "tsc --noEmit"
},
"dependencies": {
"@repo/types": "workspace:*"
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
"@repo/eslint-config": "workspace:*"
}
}
Package tsconfig.json
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Creating a New Package
1. Create directory structure
mkdir -p packages/newpkg/src
2. Create package.json
cat > packages/newpkg/package.json << 'EOF'
{
"name": "@repo/newpkg",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"lint": "eslint src/",
"test": "vitest run",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@repo/typescript-config": "workspace:*",
"@repo/eslint-config": "workspace:*"
}
}
EOF
3. Create tsconfig.json
cat > packages/newpkg/tsconfig.json << 'EOF'
{
"extends": "@repo/typescript-config/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
EOF
4. Create entry point
cat > packages/newpkg/src/index.ts << 'EOF'
export * from './main.js';
EOF
5. Install dependencies
pnpm install
Dependencies
Internal Dependencies (workspace:*)
{
"dependencies": {
"@repo/types": "workspace:*",
"@repo/core": "workspace:*"
}
}
Rules:
- Always use
workspace:*for internal packages - Dependencies must be listed explicitly
- Turborepo handles build order via
^build
Adding Dependencies
# Add to specific package
cd packages/core
pnpm add zod
# Add dev dependency
pnpm add -D vitest
# Add internal dependency
pnpm add @repo/types@workspace:*
# Add to root (tooling only)
cd ../..
pnpm add -D -w turbo
Shared Config Packages
Common pattern for eslint and typescript configs:
packages/
├── eslint-config/
│ ├── package.json
│ └── index.js
└── typescript-config/
├── package.json
├── base.json
└── node.json
eslint-config/package.json:
{
"name": "@repo/eslint-config",
"version": "0.0.0",
"private": true,
"exports": {
".": "./index.js"
}
}
typescript-config/package.json:
{
"name": "@repo/typescript-config",
"version": "0.0.0",
"private": true,
"exports": {
"./base.json": "./base.json",
"./node.json": "./node.json"
}
}
Pipeline Configuration
Task Dependencies
{
"tasks": {
"build": {
"dependsOn": ["^build"], // Build deps first
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"], // Build self first
"outputs": []
},
"lint": {
"dependsOn": ["^build"], // Need types from deps
"outputs": []
}
}
}
Filtering
# Build specific package
pnpm build --filter=@repo/core
# Build package and its dependencies
pnpm build --filter=@repo/core...
# Build package and its dependents
pnpm build --filter=...@repo/core
# Build everything except one package
pnpm build --filter='!@repo/web'
Watch Mode
# Dev all packages
pnpm dev
# Dev specific packages
pnpm dev --filter=@repo/core --filter=@repo/server
Common Patterns
Exports for Submodules
When a package has multiple entry points:
{
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./storage": {
"types": "./dist/storage/index.d.ts",
"import": "./dist/storage/index.js"
},
"./validation": {
"types": "./dist/validation/index.d.ts",
"import": "./dist/validation/index.js"
}
}
}
Usage:
import { Entity } from '@repo/core';
import { Repository } from '@repo/core/storage';
import { Validator } from '@repo/core/validation';
Apps vs Packages
Packages (packages/):
- Libraries consumed by other packages/apps
- Have
exportsfield - Build to
dist/ - No
binfield
Apps (apps/):
- Runnable applications
- May have
binfield for CLIs - Often have
startscript - Consume packages but aren't consumed
Type-Only Packages
For packages that only export types:
{
"name": "@repo/types",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
}
}
The JS file can just re-export or be empty (types are stripped at runtime).
Troubleshooting
"Cannot find module @repo/xyz"
- Check
workspace:*in dependencies - Run
pnpm install - Check package is built:
pnpm build --filter=@repo/xyz - Check
exportsin package.json matches import path
Build Order Issues
- Ensure
dependsOn: ["^build"]in turbo.json - Check circular dependencies (not allowed)
- Run
pnpm buildfrom root to see full order
Cache Issues
# Clear turbo cache
rm -rf .turbo
rm -rf node_modules/.cache/turbo
# Force rebuild
pnpm build --force
Type Errors Across Packages
- Ensure dependent package is built first
- Check
tsconfig.jsonreferences are correct - Verify
exportsincludestypesfield
Commands Reference
# Install all dependencies
pnpm install
# Build everything
pnpm build
# Build specific package and deps
pnpm build --filter=@repo/core...
# Dev mode (all packages)
pnpm dev
# Run tests
pnpm test
# Lint all
pnpm lint
# Type check
pnpm check-types
# Add dependency to package
cd packages/core && pnpm add zod
# Add workspace dependency
pnpm add @repo/types@workspace:*
# Clean all build artifacts
find . -name "dist" -type d -exec rm -rf {} + 2>/dev/null
find . -name ".turbo" -type d -exec rm -rf {} + 2>/dev/null