Use Cases (Updated: 6/2/2026)

Claude Code and Turborepo: Practical Monorepo Build Guide

Design a Turborepo monorepo with Claude Code, covering tasks, cache, CI, prompts, and common mistakes.

Claude Code and Turborepo: Practical Monorepo Build Guide

Why Claude Code fits Turborepo work

A monorepo keeps several apps and shared packages in one repository. It feels simple when you only have apps/web and packages/ui, but the hard parts arrive later: build order, package boundaries, CI time, environment variables, and cache behavior.

Turborepo helps by modeling package tasks as a graph and caching repeatable work. Claude Code helps when you use it as a reviewer of that graph, not just a generator of config files. It can read package.json, explain why one package depends on another, find missing outputs, and propose a smaller CI command for the files that actually changed.

This guide assumes Turborepo v2 as of June 2, 2026. The current config key is tasks, not the old pipeline key. Keep the official references open while adapting the examples: Turborepo configuration, turbo run, Remote Caching, and Claude Code memory.

For local ClaudeCodeLab context, pair this article with monorepo management, pnpm workspace with Claude Code, and Claude Code CI/CD setup.

Target shape and three practical use cases

The target is a small TypeScript monorepo on pnpm workspace. Starting small matters because Claude Code follows explicit boundaries much better than vague names like shared and common.

flowchart LR
  web["apps/web\nCustomer app"] --> ui["packages/ui\nShared UI"]
  admin["apps/admin\nAdmin app"] --> ui
  web --> utils["packages/utils\nShared functions"]
  admin --> utils
  ui --> tsconfig["packages/tsconfig\nTS config"]
  utils --> tsconfig

The first use case is a product with a customer app and an admin app using the same buttons, forms, and validation helpers. Splitting packages/ui from packages/utils keeps visual components away from business utilities, which makes Claude Code reviews more precise.

The second use case is a content or documentation platform where the landing page, docs, and internal dashboard live together. Running every task on every pull request becomes slow, so --affected and --filter let you verify the packages touched by the change and the packages that depend on them.

The third use case is a revenue-focused SaaS. Pricing pages, signup forms, account settings, and admin features often share small packages. If lint, type-check, test, and build are stable in Turborepo, Claude Code can modify a CTA or onboarding flow while CI still catches broken shared code.

Copy the minimum repository structure

Start by fixing the directory shape. Do not create a giant packages/shared package on day one. Give each package a responsibility you can describe in one sentence.

acme-monorepo/
  apps/
    web/
      package.json
      src/
    admin/
      package.json
      src/
  packages/
    ui/
      package.json
      src/
    utils/
      package.json
      src/
    tsconfig/
      package.json
      base.json
  pnpm-workspace.yaml
  package.json
  turbo.json
  CLAUDE.md

The workspace file is intentionally small.

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

At the root, give the team scripts that call Turborepo directly. On June 2, 2026 I verified turbo@2.9.16 and pnpm@11.5.1 as published versions; in a real repository, the lockfile is what makes the workflow reproducible.

{
  "name": "acme-monorepo",
  "private": true,
  "packageManager": "pnpm@11.5.1",
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo run build",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "type-check": "turbo run type-check",
    "check": "turbo run lint type-check test build",
    "check:affected": "turbo run lint type-check test build --affected"
  },
  "devDependencies": {
    "turbo": "^2.9.16",
    "typescript": "^5.8.3"
  }
}

Internal packages should use workspace:* so the package manager resolves them from this repository instead of accidentally pulling a package from a registry.

{
  "name": "@acme/ui",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "lint": "eslint src --max-warnings=0",
    "type-check": "tsc -p tsconfig.json --noEmit",
    "test": "vitest run"
  },
  "devDependencies": {
    "@acme/tsconfig": "workspace:*",
    "typescript": "^5.8.3",
    "vitest": "^3.1.0",
    "eslint": "^9.25.0"
  }
}

Configure turbo.json with tasks and outputs

The root turbo.json is the contract for the repository. Each key inside tasks maps to package scripts with the same name. ^build means “run build in dependency packages first.” That one symbol is the difference between a flaky monorepo and a predictable one.

{
  "$schema": "https://turborepo.dev/schema.json",
  "globalDependencies": ["pnpm-lock.yaml", "tsconfig.base.json", ".env.example"],
  "globalEnv": ["NODE_ENV"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**", "out/**"]
    },
    "lint": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "type-check": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

outputs tells Turborepo what artifacts are safe to restore from cache. If it is too narrow, cache hits will not help. If it is too broad, you store temporary framework caches and make CI harder to inspect. For Next.js, excluding .next/cache/** is usually the safer default.

Package-specific config is useful when one app has a different build artifact. Array fields replace inherited values by default, so use $TURBO_EXTENDS$ when you want to append to the root setting.

{
  "extends": ["//"],
  "tasks": {
    "build": {
      "outputs": ["$TURBO_EXTENDS$", ".next/**", "!.next/cache/**"],
      "env": ["NEXT_PUBLIC_API_URL"]
    }
  }
}

Make CI and verification boring

Turborepo becomes valuable when CI stops repeating work. The trap is that affected-package detection needs Git history. In GitHub Actions, use fetch-depth: 0; otherwise a shallow checkout can make every package look changed.

name: turbo-ci

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  verify:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: pnpm/action-setup@v4
        with:
          version: 11.5.1
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm turbo run lint type-check test build --affected
      - run: pnpm turbo run build --dry=json > turbo-plan.json
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: turbo-plan
          path: turbo-plan.json

For Remote Cache, link the repository locally with pnpm dlx turbo login and pnpm dlx turbo link, then provide TURBO_TOKEN and TURBO_TEAM in CI secrets. Treat logs as artifacts too: do not print API keys, customer data, or session tokens from build scripts.

Keep a small command set in CLAUDE.md so Claude Code does not have to rediscover it.

pnpm turbo run build --dry=json
pnpm turbo run build --filter=@acme/web...
pnpm turbo run test --filter=...[origin/main]
pnpm turbo run lint --filter=!./apps/docs
pnpm turbo run build --cache=local:rw,remote:r
pnpm turbo run build --force

Prompt Claude Code with boundaries

The prompt should describe the repository boundary, the forbidden moves, and the exact verification commands. That gives Claude Code a working frame instead of a vague request.

This repository is a Turborepo v2 + pnpm workspace monorepo.

Boundaries:
- apps/* are deployable applications.
- packages/ui contains visual components only.
- packages/utils contains framework-independent functions only.
- packages/* must not depend on apps/*.
- turbo.json must use tasks, not pipeline.

Task:
1. Read package.json and turbo.json, then explain the task dependency graph.
2. Point out missing or suspicious outputs for caching.
3. If changes are needed, make the smallest safe diff.
4. Run these commands and report the result:

pnpm turbo run lint type-check test build --affected
pnpm turbo run build --dry=json

This prompt reduces the most common AI failure: a large, unrelated rewrite. It also helps Claude Code catch bad architecture proposals, such as putting HTTP calls in packages/ui, importing Next.js from packages/utils, or treating full-repo builds as the only CI strategy.

Mistakes to avoid

The first mistake is copying an old pipeline example. Turborepo v2 uses tasks. If you are migrating an older repo, ask Claude Code to convert the config and explain each changed key against the official docs.

The second mistake is caching too much. Do not cache node_modules/**, temporary logs, or framework internals that are not deployable output. Cache build artifacts, not the whole working directory.

The third mistake is using --affected with shallow Git history. If the base and head commits are not available, Turborepo cannot compute the right package set. This usually shows up as a CI job that suddenly runs every package.

The fourth mistake is giving Claude Code too many tasks at once. Introduce Turborepo, then root scripts, then CI. Do not combine that with a package split, ESLint migration, and dependency upgrade in the same request.

The fifth mistake is over-sharing code. A huge packages/shared package makes every change look global. Smaller packages with names like ui, utils, contracts, and tsconfig make the graph readable to both people and Claude Code.

Monetization and next steps

Turborepo is not only a speed tool. It protects revenue paths. A content site can block publication when a build breaks. A SaaS can verify pricing, signup, account settings, and admin code in one repeatable workflow. A consulting team can turn shorter CI runs into shorter review queues.

ClaudeCodeLab can help teams turn this into an operating practice: CLAUDE.md, package boundaries, CI commands, review prompts, and rollout rules. For self-study, continue with monorepo management and CI/CD setup. For team adoption, start from training and consultation.

What happened when I tried it

Masa applied this pattern to a small setup with two Vite apps and one shared UI package. The first run executed every task, while later runs showed cache hits for build and type-check work. The rough part was an early outputs setting that saved framework internal cache files and made CI artifacts noisy. The cleaner version was the one shown here: v2 tasks, narrow outputs, affected-package verification, and a Claude Code prompt that reviews boundaries before changing files.

#Claude Code #Turborepo #monorepo #build tools #performance
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.