Monorepo with Turborepo: The Practical Guide for TypeScript Teams

Turborepo adds intelligent caching and parallel task execution to TypeScript monorepos. Here's a complete setup guide and an honest look at where it shines and where it struggles.

Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

May 17, 2026
6 min read
Monorepo with Turborepo: The Practical Guide for TypeScript Teams

Turborepo is the right choice for managing a TypeScript monorepo when your repo has 2 to 10 packages and your team wants fast builds without a dedicated platform engineering team. It adds two things to a standard pnpm or npm workspaces setup: intelligent task caching (only rebuild what changed) and parallel task execution (run builds and tests across packages at the same time). Below that scale, workspaces alone are sufficient. Above 10 packages, Turborepo still works but Nx or Bazel may be worth evaluating.

Why Monorepos at All

One AI engineering post, weekly

LLM benchmarks, prompt techniques, and token-cost breakdowns — not another AI news roundup.

Before the tooling, the reason. A monorepo puts multiple related packages in one git repository. For a TypeScript product, that might be:

  • packages/ui - shared React components
  • packages/types - shared TypeScript interfaces
  • packages/utils - shared utility functions
  • apps/web - Next.js web app
  • apps/admin - internal admin panel
  • apps/api - Express or Fastify API server

The alternative is separate repositories for each of these, with npm publish/install cycles between them. The monorepo eliminates that cycle: packages/ui changes are immediately available in apps/web without publishing. You can make an atomic commit that changes a type in packages/types and updates every app that uses it simultaneously.

The cost: build systems need to understand package dependencies to only rebuild what changed. Without tooling, a monorepo's build script re-runs everything, every time.

What Turborepo Adds

Intelligent caching. Turborepo hashes the inputs to each task (source files, environment variables, dependencies) and caches the output. On the next run, if nothing changed, it restores from cache instead of rebuilding. This makes subsequent builds near-instant for unchanged packages.

Parallel task execution. Turborepo reads your package dependency graph and runs tasks in parallel when safe. If apps/web depends on packages/ui, Turborepo builds packages/ui first, then starts apps/web as soon as packages/ui finishes - while simultaneously building packages/admin if it has different dependencies.

Remote cache. Share the task cache across your team (and CI) via Vercel or a self-hosted cache server. When a colleague builds the same packages with the same inputs, you get their cached output. CI runs hit the cache instead of rebuilding from scratch.

Team workspace

Ship faster with chat, meetings, and projects in one place — Zlyqor.

Start free

Setup Walkthrough

Step 1: Create the workspace structure.

mkdir my-monorepo
cd my-monorepo
pnpm init

Create pnpm-workspace.yaml:

packages:
  - "apps/*"
  - "packages/*"

Create the directory structure:

mkdir -p apps/web apps/admin packages/ui packages/types packages/utils

Step 2: Install Turborepo.

pnpm add turbo --save-dev -w

The -w flag installs at the workspace root.

Step 3: Create turbo.json.

This is Turborepo's pipeline configuration:

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

The "^build" syntax means "run build in all dependencies first." So if apps/web depends on packages/ui, Turborepo automatically builds packages/ui before apps/web.

Step 4: Add workspace root scripts to package.json.

{
  "scripts": {
    "build": "turbo build",
    "test": "turbo test",
    "lint": "turbo lint",
    "dev": "turbo dev"
  }
}

Step 5: Configure each package.

Each package needs a package.json with a name, version, and the same script names:

{
  "name": "@myapp/ui",
  "version": "0.0.1",
  "scripts": {
    "build": "tsc --outDir dist",
    "lint": "eslint ."
  }
}

Step 6: Define inter-package dependencies.

In apps/web/package.json, reference the local package:

{
  "dependencies": {
    "@myapp/ui": "workspace:*",
    "@myapp/types": "workspace:*"
  }
}

Run pnpm install at the root and pnpm creates symlinks between packages.

Running Tasks

# Build everything, in dependency order, with caching
pnpm turbo build

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

# Build all apps (not packages)
pnpm turbo build --filter="./apps/*"

# Run dev servers for all apps
pnpm turbo dev

# Run dev server for just the web app
pnpm turbo dev --filter=web

The --filter flag is essential for large monorepos where you only want to work on one app at a time.

Remote Caching with Vercel

Vercel provides a free remote cache for Turborepo. Set it up:

npx turbo login
npx turbo link

After linking, every turbo build run uploads its output to Vercel's cache. On CI, the same build gets restored from cache. For teams of 2-5 developers, this can reduce CI build times from 5 minutes to under 30 seconds for unchanged code.

Self-hosted remote cache: If you don't want to use Vercel, ducktape and turborepo-remote-cache are open source implementations. Configure in turbo.json:

{
  "remoteCache": {
    "apiUrl": "https://your-cache-server.com"
  }
}

Realistic Assessment

Turborepo is excellent for 2-10 package repos. The setup is straightforward, the caching works reliably, and the performance gains on repeat builds are substantial.

Above 10 packages, cracks appear. The --filter syntax becomes essential but also more complex. Task pipeline debugging gets harder. Teams that grow past ~20 packages often evaluate Nx, which has more sophisticated dependency graph tooling and better workspace generators.

The other limitation: Turborepo is purely a build orchestration tool. It doesn't help with shared ESLint configuration, TypeScript project references, shared testing setup, or changelogs. You'll build those patterns separately. Nx provides more of an opinionated full-stack monorepo solution, at the cost of more complexity upfront.

For most teams building a Next.js app with shared components and types, Turborepo is the right amount of tooling.

Best Practices for TypeScript Monorepos with Turborepo

  • Use TypeScript project references to enable incremental builds. Configure each package's tsconfig.json with composite: true and reference them in the root tsconfig.json. Turborepo's caching works alongside this, but project references give you faster type-checking in editors.
  • Keep turbo.json minimal. Only define tasks that need special pipeline rules. For simple scripts like lint, you can omit dependsOn and outputs — Turborepo will still cache them based on file hashes.
  • Use --filter aggressively. When working on a single app, always filter to avoid building unrelated packages. This keeps dev loop fast.
  • Set up remote caching early. Even for a solo developer, remote cache helps when switching branches or machines. For teams, it's essential to avoid redundant CI builds.
  • Version your packages with changesets. Turborepo doesn't handle versioning. Use @changesets/cli to manage changelogs and publish versions for packages that are published to npm.

Common Pitfalls and How to Avoid Them

  • Missing outputs in turbo.json: If you don't specify outputs, Turborepo won't cache the build artifacts. Always list the output directories (e.g., dist/**, .next/**).
  • Incorrect dependsOn: The ^ prefix means "dependencies' tasks." Forgetting it can cause out-of-order builds. Double-check your pipeline.
  • Environment variable mismatches: Turborepo hashes environment variables. If CI has different env vars than local, cache misses occur. Use globalDependencies in turbo.json to include env vars that affect all tasks.
  • Circular dependencies: Turborepo will error if packages have circular dependencies. Keep your dependency graph acyclic.

Keep Reading


Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace - chat, projects, time tracking, AI meeting summaries, and invoicing - in one tool. Try it free.

Frequently Asked Questions

What is a monorepo with Turborepo for TypeScript teams?

A monorepo with Turborepo is a single Git repository containing multiple TypeScript packages (e.g., shared UI components, types, utilities, and apps like Next.js or Express). Turborepo adds intelligent caching and parallel task execution on top of pnpm or npm workspaces, so only changed packages are rebuilt and tasks run in dependency order.

How does Turborepo work in a TypeScript monorepo?

Turborepo reads a `turbo.json` pipeline configuration that defines tasks (build, test, lint) and their dependencies. It hashes inputs (source files, env vars, dependencies) and caches outputs. On subsequent runs, if inputs haven't changed, it restores from cache instead of re-executing. It also runs independent tasks in parallel, respecting the dependency graph.

What are the best practices for setting up a Turborepo monorepo?

Best practices include: using TypeScript project references for incremental type-checking, keeping `turbo.json` minimal with only necessary pipeline rules, using `--filter` to limit builds to specific apps, setting up remote caching early (via Vercel or self-hosted), and using changesets for versioning. Also ensure `outputs` are correctly specified to enable caching.

How much does Turborepo cost?

Turborepo itself is open-source and free. The remote caching service from Vercel has a free tier (up to 5 users, 6 GB cache) and paid plans for larger teams. Self-hosted remote cache alternatives like `ducktape` are also free. There are no licensing costs for using Turborepo.

Is Turborepo worth it in 2026?

Yes, for TypeScript teams with 2–10 packages, Turborepo remains a solid choice in 2026. It's lightweight, easy to set up, and significantly speeds up builds via caching. However, for larger monorepos (20+ packages), Nx or Bazel may offer better scalability and tooling. Turborepo's simplicity is its strength for small to medium projects.

What are the alternatives to Turborepo for TypeScript monorepos?

Alternatives include Nx (more feature-rich, with generators and dependency graph visualization), Bazel (highly scalable but complex), and Lerna (less actively maintained). For very small projects, plain pnpm workspaces may suffice without a build orchestrator.

Can Turborepo work with npm or yarn workspaces?

Yes, Turborepo works with npm, yarn, and pnpm workspaces. The setup is similar: define workspaces in `package.json` or `pnpm-workspace.yaml`, install Turborepo, and configure `turbo.json`. pnpm is recommended for its strict dependency resolution and speed.

#turborepo#monorepo#typescript#pnpm#build-tools

// discussion

Comments

0/4000
Mahmudul Haque Qudrati — CEO & ML Engineer at Pristren

Mahmudul Haque Qudrati

CEO & ML Engineer

Visionary technologist, software engineer, and machine learning specialist. Founder and CEO of Pristren, directing engineering teams that ship production-grade AI/ML pipelines, mission-critical full-stack applications, and developer tooling. Creator of Zlyqor, the unified team workspace platform. Author of 540+ technical guides and benchmark research reports on large language models, agentic workflows, Model Context Protocol (MCP), and modern web stacks.

PristrenZlyqor