Claude Code for Turborepo: Monorepo Build Pipelines, Caching, and Workspace Management — Claude Skills 360 Blog
Blog / Development / Claude Code for Turborepo: Monorepo Build Pipelines, Caching, and Workspace Management
Development

Claude Code for Turborepo: Monorepo Build Pipelines, Caching, and Workspace Management

Published: July 19, 2026
Read time: 8 min read
By: Claude Skills 360

Monorepos get slow as they grow — rebuilding everything on every change defeats the purpose. Turborepo fixes this with task pipelines and content-addressed caching: only rebuild what changed, cache outputs locally and remotely, and share code between apps without the complexity of publishing internal packages.

Claude Code generates Turborepo configurations, pipeline definitions, and the workspace structure that makes monorepos fast.

Initial Setup

Convert our apps/api and apps/web monorepo to Turborepo.
We want: lint, build, and test tasks with correct dependency ordering.
Build web only after api types are built.
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],  // "^" means: run build in dependencies first
      "outputs": [
        "dist/**",
        ".next/**",
        "!.next/cache/**"       // Exclude Next.js build cache from Turborepo cache
      ]
    },
    "test": {
      "dependsOn": ["^build"],  // Tests can run only after deps are built
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": [],
      "cache": true             // Lint results are cached per file hash
    },
    "dev": {
      "cache": false,           // Never cache dev servers
      "persistent": true        // Long-running process
    },
    "type-check": {
      "dependsOn": ["^build"],
      "outputs": ["*.tsbuildinfo"]
    },
    "generate": {
      "cache": false            // Code generation should always run
    }
  }
}
// package.json (root)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "type-check": "turbo run type-check",
    "clean": "turbo run clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^1.13.0"
  }
}

Workspace Structure

Design the package structure for our monorepo.
We have: Next.js web app, Express API, React Native mobile app.
Share: UI components, API client, auth utilities, TypeScript config.
my-monorepo/
├── apps/
│   ├── web/              # Next.js — depends on @repo/ui, @repo/api-client
│   ├── api/              # Express — depends on @repo/auth
│   └── mobile/           # React Native — depends on @repo/ui (native), @repo/api-client
├── packages/
│   ├── ui/               # Shared React components (React + React Native)
│   ├── api-client/       # Typed API client generated from OpenAPI
│   ├── auth/             # Auth utilities, JWT validation
│   └── typescript-config/ # Shared tsconfig presets
├── turbo.json
└── package.json
// packages/typescript-config/package.json
{
  "name": "@repo/typescript-config",
  "version": "0.0.0",
  "private": true,
  "files": ["*.json"],
  "exports": {
    "./base.json": "./base.json",
    "./nextjs.json": "./nextjs.json",
    "./react-library.json": "./react-library.json"
  }
}
// packages/typescript-config/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "isolatedModules": true
  }
}
// apps/web/tsconfig.json — extends shared config
{
  "extends": "@repo/typescript-config/nextjs.json",
  "compilerOptions": {
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./src/*"] }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Internal Package Development

Create the @repo/ui package with a Button component
that works in both the Next.js web app and React Native mobile app.
// packages/ui/src/Button/index.tsx
// Platform-agnostic interface — implementations below
export type ButtonVariant = 'primary' | 'secondary' | 'ghost';
export type ButtonSize = 'sm' | 'md' | 'lg';

export interface ButtonProps {
  label: string;
  onPress: () => void;
  variant?: ButtonVariant;
  size?: ButtonSize;
  disabled?: boolean;
  loading?: boolean;
}
// packages/ui/src/Button/Button.web.tsx
import { ButtonProps } from './index';

const sizeClasses = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-base', lg: 'px-6 py-3 text-lg' };
const variantClasses = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50',
  secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
  ghost: 'text-blue-600 hover:bg-blue-50',
};

export function Button({ label, onPress, variant = 'primary', size = 'md', disabled, loading }: ButtonProps) {
  return (
    <button
      onClick={onPress}
      disabled={disabled || loading}
      className={`${sizeClasses[size]} ${variantClasses[variant]} rounded-md font-medium transition-colors`}
    >
      {loading ? <span className="animate-spin">⟳</span> : label}
    </button>
  );
}
// packages/ui/src/Button/Button.native.tsx
import { TouchableOpacity, Text, ActivityIndicator, StyleSheet } from 'react-native';
import { ButtonProps } from './index';

export function Button({ label, onPress, variant = 'primary', disabled, loading }: ButtonProps) {
  return (
    <TouchableOpacity
      onPress={onPress}
      disabled={disabled || loading}
      style={[styles.base, styles[variant], disabled && styles.disabled]}
    >
      {loading
        ? <ActivityIndicator color={variant === 'primary' ? '#fff' : '#2563eb'} />
        : <Text style={[styles.label, variant !== 'primary' && styles.labelDark]}>{label}</Text>
      }
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  base: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 8, alignItems: 'center' },
  primary: { backgroundColor: '#2563eb' },
  secondary: { backgroundColor: '#f3f4f6' },
  ghost: { backgroundColor: 'transparent' },
  disabled: { opacity: 0.5 },
  label: { color: '#fff', fontWeight: '600' },
  labelDark: { color: '#1f2937' },
});
// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    "./button": {
      "react-native": "./src/Button/Button.native.tsx",
      "default": "./src/Button/Button.web.tsx"
    }
  },
  "scripts": {
    "build": "tsc",
    "lint": "eslint src/",
    "type-check": "tsc --noEmit"
  },
  "peerDependencies": {
    "react": ">=18",
    "react-native": ">=0.71"
  },
  "devDependencies": {
    "@repo/typescript-config": "*"
  }
}

Remote Caching

Set up remote caching so the CI cache is shared with local development.
We use Vercel as the remote cache.
# Authenticate with Vercel remote cache (one-time)
npx turbo login
npx turbo link

# Now builds use remote cache — first push fills it, subsequent pulls are instant
# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

jobs:
  build-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      
      - name: Install dependencies
        run: npm ci

      - name: Build, lint, and test (with remote cache)
        run: turbo run build lint test type-check
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}
          # First CI run populates the remote cache
          # Subsequent runs — even on other machines — restore from it

With remote caching, turbo run build on a PR that only touches apps/web restores the packages/* build outputs from cache and only rebuilds what changed. CI that took 8 minutes drops to under 2 minutes.

Running Specific Workspaces

# Build only the web app and its dependencies
turbo run build --filter=web

# Build everything that depends on the ui package (when you change it)
turbo run build --filter=...@repo/ui...

# Run tests in packages that changed since main
turbo run test --filter=[main]

# Run dev for only web and api
turbo run dev --filter=web --filter=api

For the Kubernetes deployment that runs the apps built in this pipeline, see the GitOps guide. For monorepo-specific CI patterns including affected file detection, see the GitHub Actions guide. The Claude Skills 360 bundle includes monorepo skill sets for Turborepo configuration, workspace patterns, and pnpm workspace setups. Start with the free tier to try monorepo scaffold generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free