Monorepo Tooling

Turborepo

Turborepo is a high-performance build system for JavaScript monorepos. It runs tasks across packages, caches the results and skips anything that has not changed.

advanced13 min readUpdated Sep 15, 2026
turbo.json
json
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
Maintained by
Vercel
Works with
npm, pnpm, yarn, bun
Config
turbo.json
Caching
Local and remote
Pipeline
dependsOn
Language
Written in Rust

Why it matters

Why monorepos need a build system

Never rebuild the same thing

Tasks are hashed by their inputs, and unchanged work is restored from cache instead of being re-run.

Parallel and ordered

Independent tasks run in parallel while the pipeline enforces the order that dependencies require.

Built for monorepos

Run one command across every package, filter to what changed and share a single cache across the team.

The big picture

The three ideas behind Turborepo

A task pipeline, content-based hashing, and a cache that makes unchanged work free.

The pipeline

Order

A declarative graph of tasks and their dependencies, defined in turbo.json.

Content-based hashing

Identify

Each task's inputs are hashed into a key that decides whether the work must run again.

The cache

Speed

Task outputs are stored and restored by that hash, locally or in a shared remote cache.

Turborepo at a glance

The core of Turborepo

turbo.json

Defines tasks, their dependencies and their outputs.

dependsOn

Control ordering, such as building dependencies before the packages that use them.

Hashing

Inputs are hashed so a task only reruns when something relevant changes.

Caching

Store outputs and logs, then restore them on the next run.

Remote cache

Share cached results across machines and CI.

Filtering

Run tasks only for packages affected by a change.

A short history

From internal tool to the monorepo default

  1. 2021

    Turborepo released

    Jared Palmer introduces a fast build system for JavaScript monorepos.

    21
  2. 2022

    Acquired by Vercel

    Development accelerates with a focus on remote caching and CI.

    22
  3. 2023

    Turborepo 1.10 and 2.0

    A rewritten engine in Rust and a new task configuration format ship.

    23
  4. 2024

    Remote caching matures

    Shared caching across teams and CI becomes a core workflow.

    24
  5. Today

    The monorepo default

    A common choice alongside pnpm workspaces for large repositories.

    Today

The complete guide

Turborepo: Everything you need to know

What is Turborepo?

Turborepo is a high-performance build system for JavaScript monorepos. It runs tasks across all the packages in your repository, respects the dependency order between them, and caches results so that unchanged work is never repeated.

A monorepo without a build system quickly becomes painful. Running build and test in every package by hand is slow, and a naive script re-runs everything even when only one package changed. Turborepo solves both: a declarative task pipeline handles ordering, and content-based hashing makes unchanged tasks effectively free.

The task pipeline

The pipeline lives in turbo.json. Each task declares its dependencies and outputs.

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "lint": {},
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

dependsOn with a caret (^build) means “build my dependencies first”. Without the caret, it refers to a task in the same package. outputs tells Turborepo what to cache, and cache: false opts a task like dev out because it runs indefinitely.

Running tasks

One command runs a task across the whole repository, in the correct order and in parallel where possible.

turbo run build
turbo run test lint
turbo run dev --filter=web

Turborepo builds a graph from dependsOn, runs independent tasks concurrently and queues dependent ones. The result is the fastest possible schedule that still respects the constraints between packages.

Caching

Caching is the feature that changes how a monorepo feels. For each task, Turborepo hashes the inputs — source files, dependencies, environment variables and configuration — and stores the outputs and logs against that hash.

On the next run, if the hash is unchanged, the task is skipped and its outputs are restored from cache. The logs are replayed too, so the output looks the same without the work being done. On a warm cache, a full turbo run build across dozens of packages can finish in seconds.

Remote caching

A local cache only helps one machine. Remote caching shares the cache across the team and CI.

  • One developer builds a package; the result is uploaded.
  • Another developer checks out the same commit and restores it instantly.
  • CI restores the same artifacts, skipping work it has already done elsewhere.

This turns the cache into a shared asset and is often the single biggest CI speedup for a monorepo. Vercel offers a hosted remote cache, and self-hosted options exist for teams that need them.

Filtering

Large monorepos rarely need to run every task. Filtering targets a subset of packages.

# only packages affected by changes since main
turbo run test --filter="...[origin/main]"

# one package and its dependencies
turbo run build --filter=web...

# only packages that depend on @repo/ui
turbo run build --filter=...@repo/ui

The [origin/main] syntax asks Turborepo to compute which packages changed and include their dependents. In CI this means a pull request touching one package only builds and tests what it can actually affect.

Workspaces and structure

Turborepo does not manage dependencies itself — that is the package manager’s job. You define workspaces with pnpm, npm, yarn or bun, and Turborepo layers the pipeline and cache on top.

repo/
├── apps/
│   ├── web/          # a deployable app
│   └── docs/
├── packages/
│   ├── ui/           # a shared component library
│   └── config/       # shared config
├── package.json
├── pnpm-workspace.yaml
└── turbo.json

Apps consume shared packages through the workspace protocol, and Turborepo understands that dependency graph when it orders tasks. The combination of pnpm workspaces for linking and Turborepo for task running is the most common modern monorepo setup.

Best practices

  • Declare outputs for every cacheable task.
  • Use dependsOn with ^ to express dependency order.
  • Mark long-running tasks such as dev with cache: false.
  • Enable remote caching in CI for the largest gains.
  • Filter by changed packages in CI to keep pipelines fast.
  • Keep turbo.json at the repository root and share it across packages.
  • Add a root script so the whole team runs the same commands.

Common mistakes

  • Forgetting outputs and losing the cache benefit.
  • Running tasks in an arbitrary order instead of using dependsOn.
  • Caching a persistent task like a dev server.
  • Including volatile environment variables in the hash unnecessarily.
  • Running the full pipeline in CI when filtering would skip unaffected packages.
  • Treating Turborepo as a replacement for a package manager.

Where to go next

Turborepo turns a monorepo from a burden into an advantage. Pair it with pnpm for workspace linking, understand the npm and Node.js foundations, and keep individual packages building with Vite. Then add a root turbo run build to a repository with more than one package and watch the second run finish almost instantly.

Configuring a task

Declare outputs so Turborepo can cache and restore them. Without outputs, the task reruns every time.

Prefer
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    }
  }
}
Avoid
{
  "tasks": {
    "build": {}
    // nothing cached,
    // every run rebuilds
  }
}

Ordering work

dependsOn builds dependencies first. Running tasks in an arbitrary order produces missing-artifact errors.

Prefer
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"]
    }
  }
}
Avoid
# hoping the packages
# happen to build in the
# right order

FAQ

Frequently asked questions

Keep learning

Related topics from the roadmap.

$ start learning

Ready to start learning Turborepo?

Our interactive tutorial walks you through Turborepo step by step — with quizzes and real code you can run in the browser.