Back to Blog

Monorepo Tooling 2026: Turborepo vs Nx, turbo clean, Workspace Setup

Set up a production monorepo in 2026 — Turborepo vs Nx comparison, pnpm workspaces configuration, shared TypeScript packages, internal design system, CI pipelin

Viprasol Tech Team
12 min read
Updated 2026

Monorepo Tooling: Turborepo vs Nx, Workspace Setup, Shared Packages, and CI Optimization

Quick answer. Both Turborepo and Nx solve slow CI by caching task results and rebuilding only what changed via dependency-aware orchestration. Turborepo has a low-config learning curve and file-based remote caching; Nx adds more concepts like executors and generators plus built-in code generation. Choose based on how much structure your team wants.

A monorepo keeps all related code in one repository. Instead of a separate repo for your web app, API, shared utilities, and design system, you have one repo with multiple packages. Dependencies between packages are local — no publishing to npm to use your own code.

The main challenge is build performance: as the repo grows, CI builds become slow if you rebuild everything on every change. The tools covered here (Turborepo, Nx) solve this with caching and dependency-aware task orchestration.


Turborepo vs Nx

TurborepoNx
Learning curveLow — minimal configMedium — more concepts (executors, generators)
Task cachingFile-based, remote cacheFile-based, remote cache, cloud cache
Code generationNone built-inGenerators for apps/libs/components
Framework supportAnyStrong Next.js, React, Angular, NestJS integration
Affected analysisTask-graph basedDeep dependency analysis with project graph
MigrationEasy to add to existing repoRequires more structure
Best forSimpler repos, existing pnpm workspacesLarge repos, multiple frameworks, strict structure

Recommendation: Start with Turborepo. If you grow into needing code generators, affected tests at scale, or framework-specific plugins, migrate to Nx.


Monorepo Structure

your-app/
├── apps/
│   ├── web/             # Next.js frontend
│   ├── api/             # Fastify backend
│   └── docs/            # Docusaurus documentation site
├── packages/
│   ├── ui/              # Shared React component library
│   ├── config/          # Shared ESLint, TypeScript, Prettier configs
│   ├── database/        # Prisma schema + generated client
│   └── utils/           # Shared utility functions
├── pnpm-workspace.yaml
├── turbo.json
├── package.json
└── tsconfig.json        # Root TypeScript config

🌐 Looking for a Dev Team That Actually Delivers?

Most agencies sell you a project manager and assign juniors. Viprasol is different — senior engineers only, direct Slack access, and a 5.0★ Upwork record across 1000+ projects.

  • React, Next.js, Node.js, TypeScript — production-grade stack
  • Fixed-price contracts — no surprise invoices
  • Full source code ownership from day one
  • 90-day post-launch support included

pnpm Workspace Setup

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
// package.json (root)
{
  "name": "your-app",
  "private": true,
  "engines": { "node": ">=22", "pnpm": ">=9" },
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "test": "turbo test",
    "lint": "turbo lint",
    "type-check": "turbo type-check",
    "clean": "turbo clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.3.0",
    "typescript": "^5.4.0"
  }
}
// packages/ui/package.json — shared component library
{
  "name": "@acme/ui",
  "version": "0.0.0",
  "private": true,
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
    "lint": "eslint src/",
    "type-check": "tsc --noEmit"
  },
  "peerDependencies": {
    "react": "^18 || ^19",
    "react-dom": "^18 || ^19"
  },
  "devDependencies": {
    "@acme/config": "workspace:*",
    "tsup": "^8.0.0"
  }
}
// apps/web/package.json — Next.js app
{
  "name": "@acme/web",
  "version": "0.0.0",
  "private": true,
  "dependencies": {
    "@acme/ui": "workspace:*",      // Local package — no npm publish
    "@acme/database": "workspace:*",
    "@acme/utils": "workspace:*",
    "next": "^15.0.0"
  }
}

Turborepo Configuration

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [".env"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],   // ^ = run deps' build first
      "outputs": [
        "dist/**",
        ".next/**",
        "!.next/cache/**"        // Don't cache Next.js build cache
      ],
      "cache": true
    },
    "dev": {
      "dependsOn": ["^build"],   // Build deps before starting dev servers
      "persistent": true,        // Long-running task (dev server)
      "cache": false
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": [],
      "cache": true
    },
    "type-check": {
      "dependsOn": ["^build"],
      "outputs": [],
      "cache": true
    },
    "clean": {
      "cache": false
    }
  },
  "remoteCache": {
    "signature": true
  }
}

The ^build convention: runs build on all dependencies before running build on the current package. If web depends on ui, Turborepo builds ui first automatically.


monorepo - Monorepo Tooling 2026: Turborepo vs Nx, turbo clean, Workspace Setup

🚀 Senior Engineers. No Junior Handoffs. Ever.

You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.

  • MVPs in 4–8 weeks, full platforms in 3–5 months
  • Lighthouse 90+ performance scores standard
  • Works across US, UK, AU timezones
  • Free 30-min architecture review, no commitment

Remote Caching

Remote cache means CI downloads cached outputs from a previous run instead of rebuilding. If packages/ui hasn't changed since the last CI run, its build output is downloaded from cache — 0ms build time for that package.

# Turborepo Remote Cache via Vercel (free)
npx turbo login
npx turbo link  # Links to your Vercel account's cache

# Self-hosted remote cache with ducktape
# (Alternative to Vercel's cache — for private/on-prem)
# See: https://github.com/ducktors/turborepo-remote-cache
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  build-test:
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}   # Vercel remote cache token
      TURBO_TEAM: ${{ secrets.TURBO_TEAM }}     # Vercel team slug

    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with: { version: 9 }

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile

      - name: Build (cached by Turborepo)
        run: pnpm turbo build

      - name: Test (cached by Turborepo)
        run: pnpm turbo test

      - name: Lint + Type check (cached)
        run: pnpm turbo lint type-check

CI time impact: Without remote cache, a full CI run might take 8 minutes. With remote cache on a PR that only touches apps/web, Turborepo skips the packages/ui and packages/database builds — total time drops to 2–3 minutes.


Shared Packages

Shared TypeScript config:

// packages/config/tsconfig.base.json
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

Shared database package with Prisma:

// packages/database/src/index.ts
import { PrismaClient } from '@prisma/client';

// Singleton pattern — avoid multiple PrismaClient instances in dev (Next.js hot reload)
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };

export const prisma = globalForPrisma.prisma ?? new PrismaClient({
  log: process.env.NODE_ENV === 'development' ? ['query', 'error'] : ['error'],
});

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma;
}

export * from '@prisma/client';  // Re-export all Prisma types
// packages/database/package.json
{
  "name": "@acme/database",
  "scripts": {
    "db:generate": "prisma generate",
    "db:migrate": "prisma migrate deploy",
    "db:studio": "prisma studio"
  },
  "devDependencies": { "prisma": "^5.0.0" },
  "dependencies": { "@prisma/client": "^5.0.0" }
}

Running Commands Across the Monorepo

# Run a command in a specific package
pnpm --filter @acme/web dev
pnpm --filter @acme/api build

# Run in all packages matching a pattern
pnpm --filter "./packages/**" build

# Run via Turborepo (with caching and dependency ordering)
turbo build --filter=@acme/web        # Build web and its deps
turbo test --filter=@acme/ui...       # Test ui and everything that depends on ui
turbo lint --filter=[HEAD^1]          # Lint only packages changed since last commit

# Generate a new component in the ui package
cd packages/ui && pnpm plop component  # If using plop for codegen

What We Bring to the Table

We set up monorepo infrastructure for engineering teams — Turborepo or Nx configuration, pnpm workspace design, shared packages, remote caching pipelines, and CI optimization that keeps build times under 5 minutes as repos grow.

Talk to our team about developer tooling and monorepo architecture.


Next Steps

Related: Next.js Monorepo + Turborepo: pnpm Workspaces & CI — structuring a Next.js monorepo with Turborepo and pnpm.

How the turbo clean Command Works in Turborepo

The turbo clean command in Turborepo wipes the local task cache and stored artifacts so your next build runs fresh, which is useful when stale outputs cause confusing results. Running it removes the contents of the cache directory inside the monorepo, forcing tasks to re-execute rather than replay cached hashes. It is distinct from cleaning your build outputs themselves, so many teams pair the turbo clean command with a workspace-level script that also removes dist folders and node_modules across packages. Reach for it when cache invalidation feels wrong, after upgrading Turborepo, or before debugging a flaky CI pipeline. At Viprasol Tech, our senior engineers take full ownership of monorepo setups, wiring cache cleanup, remote caching, and predictable task pipelines so your Turborepo and Nx workspaces stay fast and reproducible across every developer machine.

monorepoturboreponxpnpmdevops
Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 1000+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need a Modern Web Application?

From landing pages to complex SaaS platforms — we build it all with Next.js and React.

Free consultation • No commitment • Response within 24 hours

Viprasol · Web Development

Need a custom web application built?

We build React and Next.js web applications with Lighthouse ≥90 scores, mobile-first design, and full source code ownership. Senior engineers only — from architecture through deployment.