DevToolBoxGRATIS
Blogg

Turborepo Complete Guide: High-Performance Monorepo Build System (2026)

18 min readby DevToolBox Team

Turborepo is a high-performance build system for JavaScript and TypeScript monorepos. Created by Jared Palmer and now maintained by Vercel, Turborepo uses intelligent caching and task scheduling to dramatically speed up builds, tests, and linting across multi-package repositories. Whether you manage a handful of shared libraries or dozens of applications in a single codebase, Turborepo eliminates redundant work by understanding the dependency graph between your packages and only rebuilding what has changed.

TL;DR

Turborepo is a monorepo build system that uses content-aware hashing, remote caching (Vercel Remote Cache), and parallel task execution to speed up builds by 65-85%. It works with npm, pnpm, and yarn workspaces, requires minimal configuration via turbo.json, and integrates with any CI/CD platform. Turborepo understands your task dependency graph, skips work that has already been done, and shares cached artifacts across your entire team.

Key Takeaways
  • Turborepo uses content-aware hashing to skip tasks whose inputs have not changed, often reducing CI times by 65-85%.
  • Remote caching shares build artifacts across your entire team and CI, so one developer's build result benefits everyone.
  • The task pipeline system in turbo.json defines dependencies between tasks, enabling maximum parallel execution.
  • Turborepo works with npm, pnpm, and yarn workspaces without requiring you to change your existing package manager.
  • The turbo prune command generates a sparse monorepo subset for efficient Docker image builds.
  • Turborepo requires near-zero configuration compared to alternatives like Nx, making adoption straightforward for existing monorepos.

What Is Turborepo and Why Monorepos?

A monorepo is a single repository that holds multiple projects, packages, or applications. Companies like Google, Meta, and Microsoft have used monorepos for years because they simplify code sharing, enforce consistent tooling, and make cross-project changes atomic. However, as monorepos grow, build times can explode because every CI run rebuilds everything from scratch.

Turborepo solves this by acting as an intelligent task runner that sits on top of your package manager workspaces. It analyzes the dependency graph between your packages, determines which packages are affected by a change, and runs tasks in the optimal order with maximum parallelism. When a task has been run before with the same inputs, Turborepo replays the cached output in milliseconds instead of re-executing the task.

# Create a new Turborepo monorepo
npx create-turbo@latest my-monorepo

# Choose your package manager
? Which package manager do you want to use?
  > pnpm (recommended)
    npm
    yarn

# Resulting structure:
my-monorepo/
  apps/
    web/          # Next.js application
    docs/         # Documentation site
  packages/
    ui/           # Shared React component library
    config-ts/    # Shared TypeScript configuration
    config-eslint/ # Shared ESLint configuration
  turbo.json      # Turborepo configuration
  package.json    # Root workspace configuration
  pnpm-workspace.yaml  # pnpm workspace definition

Turborepo vs Nx vs Lerna: Comparison

Turborepo, Nx, and Lerna are the three most popular monorepo tools in the JavaScript ecosystem. Each has different strengths depending on your team size and project complexity.

FeatureTurborepoNxLerna
Setup complexityMinimal (one turbo.json)Moderate (nx.json + project.json)Minimal (lerna.json)
Local cachingBuilt-in, content-awareBuilt-in, computation cachingVia Nx addon
Remote cachingVercel Remote Cache (free tier)Nx Cloud (free tier)Via Nx Cloud
Task graphturbo.json pipelinesproject.json targets + nx.jsonBasic topological sort
Code generationturbo gen (basic)Extensive generators + pluginsNone built-in
Docker pruningturbo prune (built-in)Manual or community pluginManual
Plugin ecosystemMinimal (focused tool)Extensive (React, Angular, Node, etc.)Minimal
Best forSpeed-focused teams wanting minimal configEnterprise teams needing full-featured monorepo platformSimple monorepos or legacy projects

turbo.json Configuration

The turbo.json file at the root of your repository is the single configuration file for Turborepo. It defines your task pipelines, caching behavior, and global dependencies. Here is a comprehensive configuration example covering the most common settings.

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [
    "**/.env.*local",
    ".env"
  ],
  "globalEnv": ["CI", "NODE_ENV"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json", "package.json"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"],
      "env": ["NEXT_PUBLIC_API_URL", "DATABASE_URL"]
    },
    "test": {
      "dependsOn": ["build"],
      "inputs": ["src/**", "test/**", "vitest.config.*"],
      "outputs": ["coverage/**"],
      "env": ["TEST_DATABASE_URL"]
    },
    "lint": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", ".eslintrc.*", "eslint.config.*"]
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "clean": {
      "cache": false
    }
  }
}

Pipelines and the Task Graph

Turborepo pipelines define the relationships between tasks across your monorepo. When you run turbo run build, Turborepo reads the pipeline configuration to understand which tasks depend on which other tasks, then executes them in the optimal order with maximum parallelism.

The key insight is the dependsOn field. When a task lists "^build" as a dependency, the caret (^) means it depends on the build task of its package dependencies, not on the build task in the same package. This ensures that shared libraries are built before the applications that consume them.

# Run build for all packages (respects dependency graph)
turbo run build

# Run multiple tasks in the correct order
turbo run build test lint

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

# Run build for packages affected by changes since main
turbo run build --filter=...[main...HEAD]

# Run tests only for packages that changed
turbo run test --filter=[HEAD^1]

# Visualize the task graph (opens in browser)
turbo run build --graph

# Dry run: show what would execute without running
turbo run build --dry=json

Remote Caching with Vercel Remote Cache

Remote caching is the most impactful feature of Turborepo. Without it, each developer and each CI run maintains its own local cache. With remote caching enabled, build artifacts are shared across your entire team and CI infrastructure. When Developer A builds a package locally, Developer B and your CI pipeline can reuse that exact artifact instead of rebuilding.

Vercel provides a free remote cache tier for Turborepo. You can also self-host a remote cache server using open-source implementations if you need to keep artifacts within your own infrastructure.

# Step 1: Login to Vercel
turbo login

# Step 2: Link your repo to a Vercel project
turbo link

# Now all turbo commands use remote caching automatically
turbo run build
# >>> FULL TURBO (cache hit from remote)

# For CI: use a Vercel token instead of interactive login
# Set TURBO_TOKEN and TURBO_TEAM environment variables
TURBO_TOKEN=your_vercel_token \
TURBO_TEAM=your_team_slug \
turbo run build

# Disable remote caching temporarily
turbo run build --remote-only=false

# View cache status for a run
turbo run build --summarize

Workspace Setup: npm, pnpm, and yarn

Turborepo works on top of your package manager workspaces. It does not replace npm, pnpm, or yarn. Instead, it enhances them with caching and task orchestration. Here is the recommended monorepo structure for each package manager.

pnpm (Recommended)

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"

# Root package.json
{
  "name": "my-monorepo",
  "private": true,
  "packageManager": "pnpm@9.15.0",
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test"
  },
  "devDependencies": {
    "turbo": "^2.4.0"
  }
}

npm Workspaces

// Root package.json for npm workspaces
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev"
  },
  "devDependencies": {
    "turbo": "^2.4.0"
  }
}

Shared Packages and Internal Packages

Internal packages are the building blocks of a well-organized monorepo. They allow you to share code (UI components, utility functions, TypeScript configurations, ESLint configs) across applications without publishing to npm. Turborepo treats internal packages as first-class citizens and resolves them through workspace protocols.

The workspace protocol (workspace:*) tells your package manager to resolve the dependency from the local workspace instead of the npm registry. This means changes to shared packages are immediately reflected in consuming applications during development.

// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts",
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx"
  },
  "dependencies": {
    "react": "^19.0.0"
  },
  "devDependencies": {
    "@repo/config-typescript": "workspace:*",
    "typescript": "^5.7.0"
  }
}

// apps/web/package.json (consuming the shared package)
{
  "name": "web",
  "dependencies": {
    "@repo/ui": "workspace:*",
    "next": "^15.0.0",
    "react": "^19.0.0"
  }
}

// Usage in apps/web/src/app/page.tsx
import { Button } from "@repo/ui/button";
import { Card } from "@repo/ui/card";

export default function Home() {
  return (
    <Card>
      <Button onClick={() => alert("clicked")}>
        Get Started
      </Button>
    </Card>
  );
}

TypeScript Configuration in a Monorepo

Managing TypeScript across a monorepo requires a layered configuration strategy. Create a shared base tsconfig in an internal package, then extend it in each application and package. This ensures consistent compiler options while allowing per-project overrides.

// packages/config-typescript/base.json
{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

// packages/config-typescript/nextjs.json
{
  "extends": "./base.json",
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "module": "esnext",
    "moduleResolution": "bundler",
    "jsx": "preserve",
    "noEmit": true,
    "incremental": true,
    "plugins": [{ "name": "next" }]
  }
}

// apps/web/tsconfig.json
{
  "extends": "@repo/config-typescript/nextjs.json",
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"]
}

CI/CD Integration with GitHub Actions

Turborepo integrates seamlessly with GitHub Actions and other CI platforms. The key optimization is enabling remote caching so that CI runs benefit from cached artifacts produced by other runs and local development.

# .github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  TURBO_TOKEN: \${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: \${{ vars.TURBO_TEAM }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm

      - run: pnpm install --frozen-lockfile

      - name: Build, lint, and test
        run: turbo run build lint test

      - name: Build only affected packages (PRs)
        if: github.event_name == 'pull_request'
        run: turbo run build --filter=...[origin/main...HEAD]

Incremental Builds and Content Hashing

Turborepo determines whether to rerun a task by computing a content-aware hash of the task inputs. These inputs include the source files in the package, the turbo.json pipeline configuration, environment variables listed in the env key, and the hashes of dependency packages. If the hash matches a previous run, Turborepo replays the cached output files and terminal output.

This approach is more reliable than timestamp-based invalidation because it catches cases where files are restored from git or copied from another machine. Two developers with identical source code will always produce identical hashes, making remote cache sharing effective.

# View the hash inputs for a task
turbo run build --dry=json

# Output includes hash details:
# {
#   "taskId": "web#build",
#   "hash": "a1b2c3d4e5f6",
#   "inputs": {
#     "src/app/page.tsx": "sha256:...",
#     "package.json": "sha256:...",
#     "tsconfig.json": "sha256:..."
#   },
#   "hashOfExternalDependencies": "sha256:...",
#   "environmentVariables": {
#     "NEXT_PUBLIC_API_URL": "sha256:..."
#   },
#   "cache": {
#     "status": "HIT",
#     "source": "REMOTE"
#   }
# }

# Force re-execution (ignore cache)
turbo run build --force

# Generate a summary of cache performance
turbo run build --summarize

Pruning for Docker Builds

Building Docker images for monorepo applications is tricky because Docker COPY copies the entire repository, including packages your app does not depend on. This invalidates the Docker cache on every change to any package. Turborepo solves this with the turbo prune command, which generates a sparse subset of your monorepo containing only the files needed for a specific application.

# Dockerfile for a monorepo app using turbo prune
FROM node:20-alpine AS base
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@9 --activate

# Stage 1: Prune the monorepo for the target app
FROM base AS pruner
WORKDIR /app
COPY . .
RUN npx turbo prune web --docker

# Stage 2: Install dependencies
FROM base AS installer
WORKDIR /app
COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile

# Stage 3: Build the application
COPY --from=pruner /app/out/full/ .
RUN pnpm turbo run build --filter=web

# Stage 4: Production image
FROM base AS runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=installer /app/apps/web/.next/standalone ./
COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer /app/apps/web/public ./apps/web/public
CMD ["node", "apps/web/server.js"]

Generators: Scaffolding New Packages

Turborepo includes a code generation feature (turbo gen) that helps you scaffold new packages and applications in your monorepo. Generators use Plop templates under the hood, allowing you to define custom templates with Handlebars syntax.

# Generate a new package interactively
turbo gen workspace

# Create a custom generator
# turbo/generators/config.ts
import type { PlopTypes } from "@turbo/gen";

export default function generator(plop: PlopTypes.NodePlopAPI) {
  plop.setGenerator("react-package", {
    description: "Create a new React package",
    prompts: [
      {
        type: "input",
        name: "name",
        message: "Package name (e.g., button):",
      },
    ],
    actions: [
      {
        type: "add",
        path: "packages/{{ name }}/package.json",
        templateFile: "templates/package.json.hbs",
      },
      {
        type: "add",
        path: "packages/{{ name }}/src/index.ts",
        template: 'export * from "./{{ name }}";',
      },
      {
        type: "add",
        path: "packages/{{ name }}/tsconfig.json",
        templateFile: "templates/tsconfig.json.hbs",
      },
    ],
  });
}

# Run the custom generator
turbo gen react-package

Environment Variables Handling

Environment variables are a critical part of the caching equation. If a build depends on an environment variable like API_URL, Turborepo needs to know about it so the cache is invalidated when the variable changes. You declare environment variables in turbo.json using the env and globalEnv keys.

The env key is per-task and only affects the hash for that specific task. The globalEnv key applies to all tasks in the pipeline. Variables listed in globalEnv will invalidate the cache for every task when they change.

// turbo.json - Environment variable configuration
{
  "globalEnv": [
    "CI",
    "NODE_ENV",
    "VERCEL_URL"
  ],
  "globalDependencies": [
    ".env",
    ".env.local"
  ],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "env": [
        "NEXT_PUBLIC_API_URL",
        "NEXT_PUBLIC_GA_ID",
        "DATABASE_URL"
      ]
    },
    "test": {
      "env": [
        "TEST_DATABASE_URL",
        "MOCK_API"
      ]
    }
  }
}

# Use environment variable passthrough for wildcards
# turbo.json supports prefix matching:
# "env": ["NEXT_PUBLIC_*"] matches all NEXT_PUBLIC_ variables

Migration Guide: Adding Turborepo to an Existing Monorepo

Adopting Turborepo in an existing monorepo is straightforward because Turborepo works with your existing package manager and scripts. You do not need to restructure your repository or change your build tools.

  1. Step 1: Install Turborepo as a dev dependency at the root of your monorepo.
  2. Step 2: Create a turbo.json file at the repository root defining your task pipelines.
  3. Step 3: Update your root package.json scripts to use turbo run instead of calling package manager workspace commands directly.
  4. Step 4: Enable remote caching by running turbo login and turbo link.
  5. Step 5: Update CI pipelines to use turbo run with the --filter flag for affected packages.
# Step 1: Install Turborepo
pnpm add -Dw turbo

# Step 2: Create turbo.json (see configuration section above)

# Step 3: Update root package.json scripts
# Before:
#   "build": "pnpm -r run build"
#   "test": "pnpm -r run test"
# After:
#   "build": "turbo run build"
#   "test": "turbo run test"

# Step 4: Enable remote caching
turbo login
turbo link

# Step 5: Run your first cached build
turbo run build
# First run: executes everything normally
# Second run: >>> FULL TURBO (all cache hits)

Best Practices

  • Keep turbo.json as the single source of truth for task dependencies. Avoid duplicating pipeline logic in package.json scripts.
  • Always declare environment variables that affect build output in turbo.json env or globalEnv to prevent stale cache hits.
  • Use the workspace:* protocol for internal package dependencies to ensure local resolution during development.
  • Enable remote caching from day one, even for solo developers, to benefit from cached CI artifacts locally.
  • Use turbo prune for Docker builds to minimize image size and maximize layer caching.
  • Structure shared code into focused internal packages (ui, utils, config-typescript, config-eslint) rather than one monolithic shared package.
  • Use --filter to run tasks for specific packages during development instead of running everything.
  • Pin your Turborepo version across the team using packageManager in package.json or a .turbo/config.json.
  • Set up watch mode with turbo run dev for development to leverage persistent tasks and automatic restart on changes.
  • Profile task execution with --summarize to identify bottlenecks and optimize your pipeline dependencies.

Frequently Asked Questions

What is Turborepo used for?

Turborepo is a build system and task runner designed for JavaScript and TypeScript monorepos. It speeds up builds, tests, and linting by caching task results and only re-executing tasks whose inputs have changed. Turborepo works with npm, pnpm, and yarn workspaces and is commonly used by teams managing multiple applications and shared packages in a single repository.

How does Turborepo caching work?

Turborepo computes a content-aware hash for each task based on the source files, environment variables, and dependency hashes. If the hash matches a previous run, Turborepo replays the cached output files and terminal logs instead of re-executing the task. With remote caching enabled, these cached artifacts are shared across all team members and CI runs.

Is Turborepo better than Nx?

Turborepo and Nx serve different needs. Turborepo is a focused build system with minimal configuration that excels at caching and task scheduling. Nx is a full-featured monorepo platform with extensive code generators, plugins for specific frameworks, and a built-in dependency graph visualizer. Choose Turborepo for simplicity and speed, Nx for comprehensive monorepo management with batteries included.

Does Turborepo work with pnpm?

Yes. Turborepo supports pnpm workspaces natively. In fact, pnpm is the recommended package manager for Turborepo monorepos because of its strict dependency isolation and disk-efficient storage. Configure workspaces in pnpm-workspace.yaml and Turborepo will automatically detect your package structure.

What is Turborepo remote caching?

Remote caching stores build artifacts in a shared cache (Vercel Remote Cache by default) so that team members and CI pipelines can reuse cached results instead of rebuilding from scratch. When Developer A builds a package, Developer B can skip that build entirely by downloading the cached artifact. This typically reduces CI times by 65-85%.

How do I set up a Turborepo monorepo from scratch?

Run npx create-turbo@latest to scaffold a new Turborepo monorepo. This creates a starter repository with an apps directory for applications, a packages directory for shared libraries, a turbo.json configuration, and workspace configuration for your chosen package manager. You can also add Turborepo to an existing monorepo by installing turbo as a dev dependency and creating a turbo.json file.

Can I use Turborepo with Docker?

Yes. Turborepo provides the turbo prune command specifically for Docker builds. It generates a sparse subset of your monorepo containing only the package files and dependencies needed for a specific application. This results in smaller Docker images and better layer caching because unrelated package changes no longer invalidate your Docker build cache.

Is Turborepo free to use?

Yes, Turborepo is open source and free to use under the MIT license. Vercel offers a free tier for remote caching that includes generous usage limits for small teams. For larger teams or enterprises, Vercel offers paid plans with increased cache storage and additional features. You can also self-host a remote cache server using open-source implementations.

𝕏 Twitterin LinkedIn
Var detta hjälpsamt?

Håll dig uppdaterad

Få veckovisa dev-tips och nya verktyg.

Ingen spam. Avsluta när som helst.

Try These Related Tools

{ }JSON FormatterTSJSON to TypeScriptY→YAML to JSON Converter

Related Articles

pnpm Complete Guide: Fast, Disk-Efficient Package Manager (2026)

Comprehensive pnpm guide covering content-addressable storage, workspaces, strict dependency resolution, patching, overrides, CI/CD, Docker, migration from npm/yarn, catalogs, and performance benchmarks.

Next.js Advanced Guide: App Router, Server Components, Data Fetching, Middleware & Performance

Complete Next.js advanced guide covering App Router architecture, React Server Components, streaming SSR, data fetching patterns, middleware, route handlers, parallel and intercepting routes, caching strategies, ISR, image optimization, and deployment best practices.

CI/CD Guide: GitHub Actions, GitLab CI, Docker, and Deployment Pipelines

Master CI/CD pipelines. Covers GitHub Actions workflows, GitLab CI, Docker builds, deployment strategies (blue-green, canary), secrets management, monorepo CI, and pipeline optimization.