Skip to content
Knsept
Free playbook
JavaScriptEdge & Deploy

Turborepo vs Monorepo: The Difference, Explained

A monorepo is how you organize code. Turborepo is the tool that builds it fast. What each one is, how they fit together, and when you actually need Turbo.

Robert Hemingway
By Robert HemingwayDeveloper & builder
Oct 23, 20266 min read

"Turborepo vs monorepo" is a common search, but it compares two different kinds of thing. A monorepo is a way of organizing a repository. Turborepo is a tool that runs tasks inside one. You don't choose between them; you use Turborepo in a monorepo, the way you'd use a bundler in an app.

Here's what each one does, and how they work together.

What is a monorepo?

A monorepo is "a single repository containing multiple distinct projects, with well-defined relationships" (monorepo.tools, a site maintained by the Nx team). A web app, a docs site, a shared UI kit and a utilities package can all live in one repo instead of four.

A monorepo is not a monolith. The projects stay separate, with their own package.json, and they depend on each other on purpose.

Why teams use one:

  • Share code without publishing it. The app imports @acme/ui straight from the repo. No npm release, no version bumps.
  • Atomic changes. Change a shared component and every app that uses it in one commit and one PR.
  • One set of conventions. One lint config, one TypeScript setup, one CI.
  • Visibility. People (and AI agents) can see the whole system, not one slice of it.

What it costs: builds and tests get slower as the repo grows, CI gets more complex, and access control is coarser. That cost is exactly what Turborepo exists to cut.

The alternative is a polyrepo: one repository per project, each with its own CI and versions. It's simpler per repo, but shared code gets duplicated and conventions drift.

What is Turborepo?

Turborepo is a build system and task runner for JavaScript and TypeScript monorepos, maintained by Vercel and MIT licensed. Its docs sum it up: it "provides incremental task running, local and remote caching, and parallel execution." The current major version is 2.x, and the docs now live at turborepo.dev.

Turborepo doesn't replace your package manager. It sits on top of it:

  • pnpm, npm, yarn or bun workspaces link the packages together and install dependencies.
  • Turborepo decides which tasks run, in what order, in parallel where possible, and whether they need to run at all.

How they fit together

A typical Turborepo monorepo looks like this. The docs recommend "apps/ for applications and services and packages/ for everything else":

my-repo/
├── apps/
│   ├── web/        (package.json)
│   └── docs/       (package.json)
├── packages/
│   └── ui/         (package.json)
├── package.json    (workspaces + scripts)
├── turbo.json      (task pipeline)
└── pnpm-lock.yaml

Workspaces are declared by your package manager: pnpm-workspace.yaml for pnpm, or a "workspaces": ["apps/*", "packages/*"] array in the root package.json for npm, yarn and bun.

Then turbo.json describes your tasks. This is the minimal setup from the docs:

{
  "$schema": "https://turborepo.dev/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "!.next/dev/**"]
    },
    "check-types": { "dependsOn": ["^check-types"] },
    "dev": { "persistent": true, "cache": false }
  }
}

What the keys mean:

KeyWhat it does
dependsOn: ["^build"]The ^ means "run build in this package's dependencies first." So ui builds before web, which imports it.
outputsFiles to cache. With no outputs, nothing is cached. Use ["dist/**"] for Vite or tsc.
inputsFiles that affect the result. Setting it replaces the defaults; use $TURBO_DEFAULT$ to extend them.
cache: falseFor tasks with side effects, like deploys.
persistent: trueFor long-running tasks like dev servers.

Turborepo builds two graphs from this. The package graph comes from your package manager: which package depends on which. The task graph comes from turbo.json: which task waits for which. Running turbo run build walks the task graph and runs everything it can in parallel.

Caching: the main reason to use it

Before running a task, Turborepo hashes everything that could affect it: the package's source files, its package.json, its turbo.json settings, and lockfile changes. If it has seen that exact hash before, it replays the saved output and logs instead of running the task.

  • Local cache: stored in .turbo/cache. Rebuilding an unchanged package takes milliseconds.
  • Remote cache: share the cache between teammates and CI, so a build someone already ran never runs again. Vercel's Remote Cache "is free to use on all plans, even if you do not host your applications on Vercel." Connect it with npx turbo login and npx turbo link. You can also self-host any server that implements the open Remote Cache API.

Run only what changed

turbo run build --filter=web...           # web and everything it depends on
turbo run test --filter=...ui             # ui and everything that depends on it
turbo run lint --affected                 # only packages changed vs. main
turbo prune web --docker                  # a minimal repo slice for a Docker build

--affected is shorthand for --filter=...[main...HEAD], which makes it ideal for CI. turbo prune --docker writes a pruned copy of the repo to ./out, split so Docker can cache the dependency install layer.

Internal packages: JIT or compiled

Shared packages come in two main styles:

  • Just-in-time: the package exports TypeScript source and the app's bundler compiles it. The least setup, but there's no build step for Turborepo to cache.
  • Compiled: the package builds itself (for example with tsc to dist/), and that output is cached.

Start with just-in-time for simplicity. Switch a package to compiled when its build is slow enough that caching it pays off.

Do you need Turborepo?

Not on day one. Package-manager workspaces alone already link packages and run scripts across them (pnpm -r build, pnpm --filter web dev). What they don't have is caching or dependency-aware scheduling.

A good rule: start with workspaces, and add Turborepo when builds or CI start to feel slow. Adding it later is one turbo.json and a dev dependency.

Turborepo vs Nx, Lerna and the rest

ToolWhat it is
TurborepoLightweight task runner and cache on top of your existing package.json scripts.
NxA fuller monorepo platform: plugins, code generators, and distributed CI through Nx Cloud. Suits larger, multi-team repos.
pnpm / npm / yarn / bun workspacesLinking and script running. No caching. The base layer under Turborepo.
LernaBuild and publishing tool for multi-package JS repos, now maintained by the Nx team.
RushMicrosoft's monorepo manager for large web repos.
BazelGoogle's multi-language build system. Powerful, with a steeper setup (BUILD files).
moonA task runner and monorepo tool for the web ecosystem, written in Rust.

If you mostly need "make builds fast and don't rebuild what didn't change," Turborepo is the smallest step. If you want generators, plugins and enforced architecture across many teams, look at Nx.

Set one up

npx create-turbo@latest

It also works with pnpm dlx create-turbo@latest, yarn dlx and bunx. To add Turborepo to an existing repo instead, install turbo as a dev dependency at the root and add a turbo.json like the one above.

Checkpoint
Run turbo run build twice. The second run should say "FULL TURBO" and finish in a fraction of the time, because every task came from the cache.

Skills that teach your AI agent Turborepo

If you work with a coding agent, these skills.sh skills give it Turborepo know-how:

npx skills add https://github.com/vercel/turborepo --skill turborepo

Start with the official one. Community skills can lag behind: at the time of writing, some turbo.json examples in the developer-kit skill still use the old pipeline key, and the monorepo-management skill describes Lerna as in maintenance mode, though it's actively released by the Nx team. Read a skill before you trust it.

FAQ

Is Turborepo a monorepo? No. A monorepo is a repository that holds many projects. Turborepo is a build system that runs and caches tasks inside one.

Do I need Turborepo if I already use pnpm workspaces? Not at first. Workspaces link packages and run scripts. Turborepo adds caching, dependency-aware parallel runs, --affected and remote caching, which pay off once CI slows down.

Turborepo or Nx? Turborepo is lighter: one turbo.json on top of your scripts. Nx adds generators, plugins and distributed CI, which suit large multi-team repos.

Is the Turborepo Remote Cache free? Yes. Vercel's Remote Cache is free on all plans, even if you don't host on Vercel, and you can self-host one with the open Remote Cache API.

What replaced pipeline in turbo.json? tasks, since Turborepo 2.0. Run npx @turbo/codemod migrate to upgrade.

Found this useful? Share it, or follow new posts in your reader.

Robert Hemingway
Written by

Robert Hemingway

I build small, sharp tools and write down what I learn along the way: front-end craft, edge deploys, AI-assisted workflows, and the systems that hold them together.

About me