Published on

TypeScript Monorepo in 2026 — Turborepo, Nx, and Workspaces Compared

Authors

Introduction

TypeScript monorepos serve full-stack teams by centralizing shared code, versioning, and builds. Three approaches dominate: Turborepo for speed, Nx for intelligence, and npm/pnpm workspaces for minimalism. This post compares each for teams scaling in 2026.

Monorepo Benefits for Full-Stack TS Teams

Why monorepos matter:

  • Shared types: Single source of truth for API contracts, database schemas
  • Code reuse: Database utilities, validation schemas across services
  • Unified CI/CD: Single test, build, and deploy pipeline
  • Atomic commits: API and client updates happen together
  • Dependency management: One package-lock.json for the entire project
// packages/shared/src/types.ts
export interface User {
  id: number;
  email: string;
  name: string;
  createdAt: Date;
}

// packages/api/src/handlers.ts
import { User } from '@repo/shared/types';

export async function getUser(id: number): Promise<User> {
  // API handler
}

// packages/web/src/hooks.ts
import { User } from '@repo/shared/types';
import { useQuery } from '@tanstack/react-query';

export function useUser(id: number) {
  return useQuery<User>({
    queryKey: ['user', id],
    queryFn: () => getUser(id),
  });
}

Changes to shared types propagate instantly; TypeScript catches breaking changes at compile time.

Turborepo: Remote Caching and Pipeline Config

Turborepo focuses on speed and remote caching. It's ideal for CI/CD pipelines and distributed teams.

{
  "turbo": {
    "pipeline": {
      "build": {
        "dependsOn": ["^build"],
        "outputs": ["dist/**", ".next/**"],
        "cache": true
      },
      "test": {
        "dependsOn": ["^build"],
        "outputs": ["coverage/**"],
        "cache": true,
        "inputs": ["src/**", "test/**"]
      },
      "lint": {
        "outputs": [],
        "cache": true
      },
      "dev": {
        "cache": false,
        "persistent": true
      }
    },
    "globalDependencies": ["tsconfig.json", ".eslintrc.json"]
  }
}

Turborepo analyzes the dependency graph and runs tasks in parallel. Remote caching stores outputs in Vercel or self-hosted S3, speeding up CI/CD by 10-100x.

# Local build (uses cache)
pnpm turbo build --filter=@repo/api

# CI with remote cache
turbo build \
  --api="https://cache.vercel.com" \
  --token="$TURBO_TOKEN" \
  --team="my-team"

Nx: Intelligent Caching and Affected Commands

Nx tracks project dependencies and enables running only affected tasks:

{
  "nx": {
    "nxCloudUrl": "https://my-nx-cloud.com"
  },
  "projects": {
    "@repo/api": {
      "projectType": "application",
      "sourceRoot": "packages/api/src",
      "targets": {
        "build": {
          "executor": "@nx/esbuild:esbuild",
          "options": {
            "outputPath": "dist",
            "main": "packages/api/src/index.ts"
          },
          "cache": true
        },
        "test": {
          "executor": "@nx/jest:jest",
          "outputs": ["{workspaceRoot}/coverage/packages/api"],
          "cache": true
        }
      }
    }
  }
}

Nx's killer feature is affected commands. After a commit, run only tests for changed packages:

# Run tests for only the packages you modified
nx affected --targets=test

# Run build for modified packages and their dependents
nx affected --targets=build

# Visualize dependency graph
nx graph --file=graph.html

Nx is more opinionated than Turborepo; it enforces project structure and provides generators.

PNPM/Yarn/npm Workspaces Comparison

All three support workspaces. pnpm is fastest due to content-addressable storage.

{
  "name": "monorepo",
  "version": "1.0.0",
  "workspaces": [
    "packages/*",
    "services/*"
  ]
}

pnpm (recommended):

  • Fastest installation (hard links)
  • Strictest dependency management (explicit peer dependencies)
  • Smallest node_modules footprint
  • Native monorepo support

Yarn (legacy):

  • Good for large monorepos (Yarn 3 + Yarn workspaces)
  • Slower than pnpm
  • Good lock file format

npm:

  • Built-in workspaces (npm 7+)
  • Adequate for small monorepos
  • Slowest of the three
# With pnpm
pnpm install          # Links all packages
pnpm --filter @repo/api run build
pnpm add -D typescript -w  # Add to root

# With Yarn
yarn install
yarn workspace @repo/api run build
yarn add -D typescript --ignore-workspace-root-check

# With npm
npm install
npm run -w @repo/api build
npm install -D typescript -w

Shared TypeScript Configs

Create a root tsconfig.json that all packages extend:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022"],
    "skipLibCheck": true,
    "strict": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "declaration": true,
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": ".",
    "paths": {
      "@repo/*": ["packages/*/src"]
    }
  }
}

Individual packages extend with overrides:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src"],
  "exclude": ["dist", "node_modules"]
}

Shared configs ensure consistency across 10-100 packages without duplication.

Shared ESLint and Prettier Configs

Centralize linting rules in a shared package:

// packages/eslint-config/index.js
export default [
  {
    ignores: ['dist', 'node_modules', 'coverage'],
  },
  {
    files: ['**/*.ts', '**/*.tsx'],
    rules: {
      'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/no-unused-vars': 'error',
    },
  },
];

Reference in each package:

{
  "extends": ["@repo/eslint-config"]
}

Internal Package Patterns

Use scoped packages for internal libraries:

packages/
  ├── shared/          # @repo/shared
  │   ├── types/       # Database, API types
  │   ├── utils/       # Common utilities
  │   └── validation/  # Zod schemas
  ├── db/              # @repo/db
  │   └── Drizzle ORM setup, migrations
  ├── api/             # @repo/api
  │   └── Backend service
  ├── web/             # @repo/web
  │   └── Next.js frontend
  └── cli/             # @repo/cli
      └── CLI tools

Package naming pattern:

{
  "name": "@repo/api",
  "exports": {
    ".": "./dist/index.js",
    "./handlers": "./dist/handlers.js"
  }
}

Consumers import cleanly:

import { getUser } from '@repo/api/handlers';
import { User } from '@repo/shared/types';
import { db } from '@repo/db';

Building Publishable Packages

Some internal packages may publish to npm:

{
  "name": "@your-org/validation",
  "version": "1.0.0",
  "exports": {
    ".": "./dist/index.js",
    "./schemas": "./dist/schemas.js"
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build && npm run test"
  }
}

Use semantic versioning and changelogs:

# Changelog

## 1.1.0 - 2026-03-15

- Add email validation schema
- Support international phone numbers

CI Pipeline Design for Monorepos

Turborepo-based CI pipeline:

name: CI

on: [push, pull_request]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history for affected commands
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'pnpm'
      - run: pnpm install
      - run: pnpm turbo build lint test
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

For Nx, use affected to test only changed packages:

- run: nx affected --targets=lint,test,build

Choosing Between Turborepo and Nx in 2026

AspectTurborepoNx
Learning curveLowModerate
SpeedVery fastFast
Remote cachingExcellent (Vercel)Excellent (Nx Cloud)
ConfigurationMinimalModerate
GeneratorsNoYes (schematics)
IDE integrationGoodExcellent
CLI experienceSimpleFeature-rich
Best forTeams valuing speedLarge enterprises

Choose Turborepo if:

  • Speed is paramount
  • You want minimal configuration
  • Team is using Vercel/AWS

Choose Nx if:

  • You want code generators
  • You need IDE plugins
  • You have 10+ packages
  • You're building a platform

Checklist

  • Evaluate monorepo structure (flat vs nested)
  • Choose Turborepo, Nx, or native workspaces
  • Set up pnpm workspaces (pnpm-workspace.yaml)
  • Create shared tsconfig.json base
  • Build @repo/shared package for types
  • Set up @repo/db for database utilities
  • Configure ESLint + Prettier sharing
  • Add Turborepo/Nx pipeline configuration
  • Test CI build with remote caching
  • Document internal package usage

Conclusion

TypeScript monorepos accelerate full-stack development by sharing types, dependencies, and build pipelines. Turborepo offers speed with minimal overhead; Nx provides intelligence for large teams. Combined with pnpm workspaces and shared configs, you can scale to 100+ packages while maintaining type safety and build performance.