| 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