turborepo-monorepo

cr8or-space's avatarfrom cr8or-space

Guide for managing TypeScript monorepos with Turborepo and pnpm workspaces. Covers package creation, dependencies, pipeline configuration, and common patterns.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 8, 2026

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.
nameturborepo-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:

  • ^build means "build dependencies first"
  • outputs defines what to cache
  • persistent: true for long-running dev servers
  • cache: false for 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 exports field
  • Build to dist/
  • No bin field

Apps (apps/):

  • Runnable applications
  • May have bin field for CLIs
  • Often have start script
  • 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"

  1. Check workspace:* in dependencies
  2. Run pnpm install
  3. Check package is built: pnpm build --filter=@repo/xyz
  4. Check exports in package.json matches import path

Build Order Issues

  1. Ensure dependsOn: ["^build"] in turbo.json
  2. Check circular dependencies (not allowed)
  3. Run pnpm build from 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

  1. Ensure dependent package is built first
  2. Check tsconfig.json references are correct
  3. Verify exports includes types field

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