6 Turborepo vs Nx Patterns That Cut Monorepo CI Time by 70%

Published: (March 4, 2026 at 04:06 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

# Monorepo Performance Patterns (Turborepo & Nx)

Most teams move to a monorepo and see zero speed gains because they copy the README setup and stop. The real wins come from a few configuration patterns that most repos never implement.

Below are **6 concrete Turborepo and Nx patterns** that actually reduce CI time and developer wait time in production projects.

---

## 1. Upstream Build Dependencies Instead of Global Builds  

If your `build` task does not declare upstream dependencies, your cache graph is wrong.

### Before – naive Turborepo setup  

```json
{
  "tasks": {
    "build": {
      "outputs": ["dist/**"]
    }
  }
}

This builds every package independently. If ui depends on shared-types, build order is undefined.

After – dependency‑aware build graph

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

The ^build ensures dependencies build first. Turborepo now executes in parallel where possible but preserves correctness.

Result: In a 12‑package repo, this alone removed 4–6 unnecessary rebuilds per CI run.


2. Filter Changed Packages in CI Instead of Building Everything

The biggest monorepo mistake is running full builds on every PR.

Before – full CI run

- run: npx turbo build
- run: npx turbo test

Every package runs. Every time.

After – change‑based filtering (Turborepo)

- run: npx turbo build --filter='...[HEAD^1]'
- run: npx turbo test  --filter='...[HEAD^1]'

Now only changed packages and their dependents run.

Nx equivalent

- run: npx nx affected --target=build
- run: npx nx affected --target=test

Result: On a 20‑package workspace, CI typically drops from ~14 min to 3–5 min.


3. Remote Caching for Team‑Wide Build Deduplication

Local caching helps one developer. Remote caching helps the entire team.

Before – local only

npx turbo build

Every developer rebuilds the same unchanged packages.

After – remote cache enabled (Turborepo)

npx turbo login
npx turbo link

Now build artifacts are shared across the team.

Nx equivalent

npx nx connect-to-nx-cloud

Result: On a 6‑developer team, remote caching reduced cumulative daily build time by roughly 40 % (hours per week returned).


4. TypeScript Project References for Incremental Type Checking

Monorepos with 15+ TypeScript packages can spend ~30 s on type checking alone.

Before – flat tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist"
  }
}

Every package compiles from scratch.

After – composite + references

{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "outDir": "dist"
  },
  "references": [
    { "path": "../shared-types" }
  ]
}

Then run:

tsc --build

TypeScript now builds in dependency order and caches results. Incremental builds drop from ~25 s to Note: Neither Turborepo nor Nx sets this up for you; you must configure it intentionally.


5. Enforce Package Boundaries to Prevent Graph Explosion

Performance degrades when everything imports everything.

Before – no constraints

// libs/ui importing from apps/web
import { something } from '../../apps/web/internal';

Creates circular and cross‑layer coupling.

After – Nx enforce-module-boundaries

{
  "rules": {
    "@nx/enforce-module-boundaries": [
      "error",
      {
        "depConstraints": [
          {
            "sourceTag": "type:app",
            "onlyDependOnLibsWithTags": ["type:lib"]
          }
        ]
      }
    ]
  }
}

Now apps cannot be imported into libraries. Turborepo does not enforce this out of the box; you must use ESLint rules or strict folder conventions.

Result: In large workspaces, clean boundaries reduce the affected graph size by 20–30 %, directly cutting CI scope.


6. Separate Dev Tasks From Cached Tasks

Caching dev servers is useless and slows things down.

Before – dev cached unintentionally

{
  "tasks": {
    "dev": {}
  }
}

Turborepo may treat it like a regular cached task.

After – persistent and uncached

{
  "tasks": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Now local development runs cleanly without polluting cache state.

Nx equivalent

Ensure serve targets are not part of affected production pipelines.

Result: Prevents confusing cache invalidations that cause developers to distrust the system.


Turborepo vs. Nx for Performance‑Critical Workspaces

  • Fewer than 15 packages + strong existing tooling → Turborepo is usually sufficient.
  • Larger workspaces or teams needing stricter architectural enforcement → Nx provides richer tooling (e.g., module‑boundary enforcement, affected command granularity, built‑in remote caching).

Apply the patterns above regardless of the tool you choose, and you’ll see measurable reductions in CI time and developer friction.

# Optimizing Your Monorepo Build Process

If you have **30+ packages** and cross‑domain imports, **Nx** often wins because its project graph analyzes actual file imports instead of just `package.json` dependencies. That finer granularity means fewer false positives in affected builds.

> **Note:** Neither tool magically optimizes your repo. The speed gains come from:

- **Correct dependency declarations**
- **Aggressive change filtering**
- **Remote caching**
- **Incremental TypeScript builds**
- **Strict package boundaries**

If your CI is still running full builds on every push, you do **not** have a monorepo problem—you have a **configuration problem**.

## Actionable Steps

1. **Pick one of these patterns** and implement it this week.  
2. **Measure CI** before and after the change.  
3. If you cannot show a **delta in minutes saved**, you are not using your monorepo tooling correctly.

---

*Take the time to fine‑tune your setup now, and you’ll reap faster feedback loops and more efficient builds later.*
0 views
Back to Blog

Related posts

Read more »