DevToolBoxGRATIS
Blog

Monorepo Gids 2026: Turborepo vs Nx

15 minby DevToolBox

Monorepo Guide 2026: Setting Up Turborepo and Nx for Modern JavaScript Projects

Monorepos — single repositories containing multiple related packages or applications — have become the standard architecture for medium and large JavaScript projects. Companies like Google, Meta, Vercel, and Microsoft manage enormous codebases in monorepos. The tooling in 2026 has matured dramatically: Turborepo and Nx are the two leading solutions, each with a distinct philosophy. This guide covers both tools, helps you choose between them, and provides practical setup instructions for real-world project structures.

Why Use a Monorepo?

Before picking a tool, understand the benefits and trade-offs that make monorepos compelling.

  • Atomic cross-package changes — Change a shared utility and the consuming apps in one commit, with one PR
  • Shared tooling — One ESLint config, one TypeScript base config, one testing setup
  • Simplified dependency management — No version mismatch between internal packages
  • Faster CI with caching — Only rebuild and retest what actually changed
  • Code sharing without publishing — Share code between apps without npm publish cycles
  • Unified developer experience — One place to clone, one npm install, consistent commands
FeatureTurborepoNx
Learning curveLowMedium-High
Config overheadMinimalModerate
Build cachingExcellent (local + remote)Excellent (local + remote)
Task orchestrationPipeline-basedGraph-based (more powerful)
Code generationBasic generatorsFull generator system
IDE integrationGoodExcellent (Nx Console)
Best forFrontend/fullstack, Next.js shopsEnterprise, Angular, mixed stacks

Turborepo Setup

Turborepo (from Vercel) is the simpler of the two. It focuses on task running and caching with minimal configuration. It integrates naturally with npm/yarn/pnpm workspaces.

Initial Setup

# Create a new Turborepo project
npx create-turbo@latest my-monorepo

# Or add Turborepo to an existing workspace
npm install turbo --save-dev --workspace-root

# Recommended: use pnpm for best workspace performance
pnpm dlx create-turbo@latest my-monorepo --package-manager pnpm

Project Structure

my-monorepo/
├── apps/
│   ├── web/                    # Next.js app
│   │   ├── package.json
│   │   └── ...
│   ├── admin/                  # Another Next.js app
│   │   ├── package.json
│   │   └── ...
│   └── api/                    # Express/Fastify API
│       ├── package.json
│       └── ...
├── packages/
│   ├── ui/                     # Shared React component library
│   │   ├── package.json        # name: "@acme/ui"
│   │   ├── src/
│   │   │   ├── Button.tsx
│   │   │   ├── Card.tsx
│   │   │   └── index.ts
│   │   └── tsconfig.json
│   ├── config/                 # Shared configs
│   │   ├── package.json        # name: "@acme/config"
│   │   ├── eslint/
│   │   │   └── index.js
│   │   └── typescript/
│   │       ├── base.json
│   │       ├── nextjs.json
│   │       └── react-library.json
│   └── utils/                  # Shared utilities
│       ├── package.json        # name: "@acme/utils"
│       └── src/
│           ├── format.ts
│           └── validate.ts
├── turbo.json                  # Turborepo config
├── package.json                # Root workspace config
└── pnpm-workspace.yaml

turbo.json Configuration

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**"],
      "cache": true
    },
    "dev": {
      "dependsOn": ["^build"],
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    },
    "lint": {
      "outputs": [],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "*.json", ".eslintrc.*"]
    },
    "type-check": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "clean": {
      "cache": false
    }
  }
}

The ^build syntax means "run build in all dependencies first." This ensures @acme/ui is built before the apps that depend on it.

Root package.json

{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "type-check": "turbo run type-check",
    "clean": "turbo run clean && rm -rf node_modules",
    "format": "prettier --write "**/*.{ts,tsx,md,json}" --ignore-path .gitignore"
  },
  "devDependencies": {
    "turbo": "latest",
    "prettier": "^3.0.0",
    "typescript": "^5.4.0"
  },
  "packageManager": "pnpm@9.0.0"
}

pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'

Shared TypeScript Configuration

// packages/config/typescript/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "exactOptionalPropertyTypes": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

// packages/config/typescript/nextjs.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "./base.json",
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "ES2022"],
    "allowJs": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": { "@/*": ["./src/*"] }
  }
}

Internal Package Setup

// packages/ui/package.json
{
  "name": "@acme/ui",
  "version": "0.0.1",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": {
      "types": "./src/index.ts",
      "default": "./src/index.ts"
    }
  },
  "scripts": {
    "lint": "eslint src/ --ext .ts,.tsx",
    "type-check": "tsc --noEmit"
  },
  "devDependencies": {
    "@acme/config": "workspace:*",
    "@types/react": "^18.3.0",
    "typescript": "^5.4.0"
  },
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}
// packages/ui/src/Button.tsx
import { type ButtonHTMLAttributes } from 'react';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
}

export function Button({
  variant = 'primary',
  size = 'md',
  isLoading = false,
  children,
  disabled,
  className = '',
  ...props
}: ButtonProps) {
  const variantClasses = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700',
    secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200',
    danger: 'bg-red-600 text-white hover:bg-red-700',
  };

  const sizeClasses = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg',
  };

  return (
    <button
      {...props}
      disabled={disabled || isLoading}
      className={`${variantClasses[variant]} ${sizeClasses[size]} rounded-lg font-medium transition-colors ${className}`}
    >
      {isLoading ? 'Loading...' : children}
    </button>
  );
}

// packages/ui/src/index.ts — barrel export
export { Button } from './Button';
export { Card } from './Card';
export type { ButtonProps } from './Button';

Remote Caching

Remote caching is one of Turborepo's most powerful features. Build artifacts are shared across all developers and CI machines, so if a colleague already built the same commit, you get an instant cache hit instead of rebuilding.

# Vercel Remote Cache (free for Vercel users)
npx turbo login
npx turbo link

# Self-hosted remote cache (Turborepo supports any S3-compatible store)
# Set these environment variables:
# TURBO_API=https://your-cache-server.com
# TURBO_TOKEN=your-token
# TURBO_TEAM=your-team-slug

# GitHub Actions with remote cache
# .github/workflows/ci.yml
# env:
#   TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
#   TURBO_TEAM: ${{ vars.TURBO_TEAM }}

Nx Setup

Nx provides a more structured and feature-rich monorepo experience. It shines in large teams, enterprise environments, and projects with multiple frameworks (React, Angular, Node.js, etc.).

# Create new Nx workspace
npx create-nx-workspace@latest my-workspace

# Add to existing project
npx nx@latest init

# Add Next.js app
nx generate @nx/next:application web --directory=apps/web

# Add a shared library
nx generate @nx/react:library ui --directory=packages/ui --bundler=vite

# Add an Express API
nx generate @nx/express:application api --directory=apps/api

# Run affected tasks only (most powerful Nx feature)
# Only tests packages changed since last commit
nx affected --target=test

# Visualize the project dependency graph
nx graph

nx.json Configuration

{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "defaultBase": "main",
  "namedInputs": {
    "default": ["{projectRoot}/**/*", "sharedGlobals"],
    "production": [
      "default",
      "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
      "!{projectRoot}/tsconfig.spec.json",
      "!{projectRoot}/.eslintrc.json"
    ],
    "sharedGlobals": ["{workspaceRoot}/.github/workflows/ci.yml"]
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["production", "^production"],
      "cache": true
    },
    "test": {
      "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
      "cache": true
    },
    "lint": {
      "inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
      "cache": true
    }
  },
  "plugins": [
    "@nx/next/plugin",
    "@nx/eslint/plugin",
    "@nx/vite/plugin"
  ]
}

CI/CD Pipeline

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for Nx affected commands

      - uses: pnpm/action-setup@v3
        with:
          version: 9

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

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      # For Turborepo
      - name: Build
        run: pnpm turbo build --filter="...[origin/main]"
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

      - name: Test
        run: pnpm turbo test --filter="...[origin/main]"
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

      # For Nx (alternative)
      # - name: Build affected
      #   run: npx nx affected --target=build --base=origin/main
      # - name: Test affected
      #   run: npx nx affected --target=test --base=origin/main

Choosing: Turborepo vs Nx

  • Choose Turborepo if you want minimal config, are already using Vercel, your stack is primarily Next.js, or you want to get started in under an hour.
  • Choose Nx if you need code generation, have a large team that benefits from enforced project boundaries, work with multiple frameworks, or need the project graph visualization to understand dependency relationships.
  • Both are excellent choices for remote caching and affected task running, which are the core features that make monorepos fast in CI.

When working with your monorepo packages, our JSON Formatter tool can help validate package.json and turbo.json files. For generating unique IDs across your packages, try our UUID Generator. You can also read our Git Branching Strategies guide for branch management patterns that work well with monorepo workflows.

𝕏 Twitterin LinkedIn
Was dit nuttig?

Blijf op de hoogte

Ontvang wekelijkse dev-tips en nieuwe tools.

Geen spam. Altijd opzegbaar.

Try These Related Tools

{ }JSON Formatter📋YAML Formatter

Related Articles

Git Workflow Strategieën

Vergelijk Git branching-strategieën.

Docker Security Best Practices: Container Hardening Gids

Uitgebreide gids voor Docker-containerbeveiliging — minimale images, non-root gebruikers, secrets management.

CI/CD Pipeline Best Practices: GitHub Actions, Testen en Deployment

Robuuste CI/CD-pipelines met GitHub Actions — teststrategieën en deployment-patronen.