DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Turborepo 2.0: Remote Caching, Task Pipelines, and What Actually Speeds Up CI

Turborepo 2.0: Remote Caching, Task Pipelines, and What Actually Speeds Up CI

Turborepo 2.0 shipped with a rewritten task runner, first-class Watch mode, and a new turbo.json schema. After migrating two monorepos to 2.0, here's what the upgrade actually looks like.

Why Turborepo Exists

A monorepo without a build orchestrator means every npm run build rebuilds everything. 5 packages? Fine. 20 packages? You're waiting 8 minutes for CI on a 2-line change.

Turborepo solves this with:

  1. Task graph: understands which packages depend on which
  2. Local cache: skips tasks whose inputs haven't changed
  3. Remote cache: shares that cache across every machine and CI runner

The 2.0 Schema Changes

The pipeline key is gone. Tasks are now defined directly:

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

Key change: "pipeline""tasks". Automated migration:

npx @turbo/codemod migrate
Enter fullscreen mode Exit fullscreen mode

Runs in ~30 seconds, handles the rename and schema normalization.

Understanding dependsOn

This is where most people get confused.

^build — run build in all dependency packages first, then this package.

build (no caret) — run build in this package first, then this task.

[] — no dependencies, run immediately and in parallel.

{
  "tasks": {
    "build": { "dependsOn": ["^build"] },
    "test": { "dependsOn": ["build"] },
    "lint": { "dependsOn": [] }
  }
}
Enter fullscreen mode Exit fullscreen mode

With this config: all packages lint in parallel, build respects dependency order, tests wait for local build.

Remote Caching Setup

Local cache is on by default. Remote cache requires a Vercel account (free) or a self-hosted Turborepo Remote Cache server.

Vercel Remote Cache

npx turbo login
npx turbo link
Enter fullscreen mode Exit fullscreen mode

That's it. Now every turbo build on any machine shares artifacts. CI goes from 6 minutes to 45 seconds on a warm cache.

Self-Hosted Cache (if you can't use Vercel)

# ducktape/turborepo-remote-cache is the reference implementation
docker run -p 3000:3000 \
  -e TURBO_TOKEN=your-secret \
  ducktape/turborepo-remote-cache
Enter fullscreen mode Exit fullscreen mode

Then in your CI:

TURBO_API="https://your-cache.internal" \
TURBO_TOKEN="your-secret" \
TURBO_TEAM="myteam" \
npx turbo build
Enter fullscreen mode Exit fullscreen mode

Watch Mode (New in 2.0)

The biggest developer experience win in 2.0:

turbo watch dev
Enter fullscreen mode Exit fullscreen mode

Spins up dev scripts across all packages and automatically restarts affected packages when files change. Previously you needed concurrently or custom scripts to orchestrate this.

{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

persistent: true tells Turbo this task doesn't exit — don't wait for it before running dependents.

Filtering Runs

Only run tasks for changed packages:

# Only run affected packages since main
turbo build --filter='...[origin/main]'

# Only a specific package and its dependents
turbo build --filter='@acme/ui...'

# Exclude a package
turbo build --filter='!@acme/storybook'
Enter fullscreen mode Exit fullscreen mode

In GitHub Actions:

- name: Build affected packages
  run: turbo build --filter='...[HEAD^1]'
Enter fullscreen mode Exit fullscreen mode

On a monorepo with 15 packages, this typically cuts CI time by 60-80% for single-package PRs.

What Actually Moves the Needle

After optimizing three monorepos, the impact ranking:

  1. Remote cache — 10x improvement on repeated CI runs for unchanged code
  2. --filter on CI — 3-5x improvement for typical feature PRs
  3. Correct dependsOn — 1.5-2x improvement by maximizing parallelism
  4. outputs configuration — ensures cache hits are valid, prevents stale artifact bugs

The single biggest mistake: not configuring outputs. Without it, Turbo can't restore cached builds correctly.

{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "build/**"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Match your actual build output directories. Turbo stores and restores these on cache hit.

Common Gotchas

Environment variables aren't automatically hashed. Add them to globalEnv or per-task env or Turbo won't bust the cache when they change:

{
  "globalEnv": ["NODE_ENV", "CI"],
  "tasks": {
    "build": {
      "env": ["NEXT_PUBLIC_API_URL"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't cache dev tasks. They're stateful and long-running. "cache": false is the correct setting.

Workspaces must be properly linked. Turbo reads your package manager's workspace config (workspaces in root package.json for npm/yarn, packages in pnpm-workspace.yaml). Mis-configured workspaces mean Turbo can't resolve the dependency graph.

The Verdict

Turborepo 2.0 is a meaningful upgrade. The new task runner is faster, Watch mode eliminates a whole category of custom scripts, and the schema cleanup reduces cognitive overhead.

If you're on 1.x, the migration is a 30-second codemod and worth doing today. If you're evaluating Nx vs Turborepo — Turbo wins on simplicity for TS/JS monorepos without custom generators or module federation.


Building a multi-package TypeScript project and want the foundation already wired? The Ship Fast Skill Pack includes Turborepo setup, shared ESLint/TypeScript configs, and 20+ Claude Code workflow skills for $49.

Top comments (0)