正在加载,请稍候…

Monorepo Architecture with Nx and Turborepo: Setup and Best Practices

Learn how to structure JavaScript/TypeScript monorepos with Nx or Turborepo. Covers workspace setup, task pipelines, caching, code generation, and CI optimization.

Monorepo Architecture with Nx and Turborepo

Monorepos enable code sharing and consistent tooling across multiple packages. This guide compares Nx and Turborepo and covers production patterns.

Why Monorepos?

Benefits:

  • Code sharing: Shared utilities, types, and components
  • Atomic commits: Change multiple packages in one commit
  • Consistent tooling: One linter, formatter, TypeScript config
  • Simplified dependency management: No version conflicts

Turborepo Setup

# Create new monorepo with Turborepo
npx create-turbo@latest my-monorepo
cd my-monorepo
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],  // ^ means run deps first
      "outputs": ["dist/**", ".next/**"],
      "cache": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true  // Long-running process
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    }
  }
}

Workspace Structure

my-monorepo/
  apps/
    web/          # Next.js frontend
    api/          # Express backend
    docs/         # Documentation site
  packages/
    ui/           # Shared React components
    types/        # Shared TypeScript types
    utils/        # Shared utilities
    config/       # Shared configs (eslint, tsconfig)
  package.json    # Root workspace config
  turbo.json
// Root package.json
{
  "name": "my-monorepo",
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "typecheck": "turbo run typecheck"
  },
  "devDependencies": {
    "turbo": "latest"
  }
}

Shared Package Setup

// packages/ui/package.json
{
  "name": "@myapp/ui",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": "./src/index.ts"
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
    "lint": "eslint src/",
    "typecheck": "tsc --noEmit"
  },
  "peerDependencies": {
    "react": ">=18"
  }
}
// packages/ui/src/Button.tsx
export interface ButtonProps {
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
  onClick?: () => void;
}

export function Button({ children, variant = 'primary', onClick }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// packages/ui/src/index.ts
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';
// apps/web/src/page.tsx - consuming shared package
import { Button, Input } from '@myapp/ui';

export default function HomePage() {
  return (
    <div>
      <Input placeholder="Search..." />
      <Button variant="primary">Submit</Button>
    </div>
  );
}

Shared TypeScript Configuration

// packages/config/tsconfig.base.json
{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "target": "ES2022",
    "lib": ["ES2022"],
    "paths": {
      "@myapp/*": ["../../packages/*/src"]
    }
  }
}

// apps/web/tsconfig.json
{
  "extends": "@myapp/config/tsconfig.base.json",
  "compilerOptions": {
    "lib": ["ES2022", "DOM"],
    "plugins": [{ "name": "next" }]
  },
  "include": ["src/**/*", "next-env.d.ts"]
}

Nx Monorepo Alternative

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

# Generate apps and libraries
nx generate @nx/react:application web
nx generate @nx/express:application api
nx generate @nx/react:library ui --directory=shared
// nx.json
{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "test": {
      "cache": true
    }
  },
  "affected": {
    "defaultBase": "main"
  }
}

CI Optimization with Remote Caching

# Turborepo Remote Cache (Vercel)
npx turbo login
npx turbo link  # Link to Vercel team

# GitHub Actions
- name: Build
  run: turbo run build
  env:
    TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
    TURBO_TEAM: ${{ vars.TURBO_TEAM }}

# Run only affected packages
turbo run test --filter="...[HEAD^1]"
# Only packages changed since last commit

Internal Package Versioning

// packages/ui/package.json
{
  "version": "0.0.0",  // Internal - don't publish
  // OR use changesets for publishable packages
}
# Changesets for versioning
npx changeset init
npx changeset  # Create changeset for modified packages
npx changeset version  # Bump versions based on changesets
npx changeset publish  # Publish to npm

Summary

Monorepo best practices:

  • Use Turborepo for JavaScript-focused monorepos (simpler)
  • Use Nx for large enterprise codebases (more features)
  • Define clear package boundaries with explicit exports
  • Share tsconfig, eslint, and prettier configs
  • Use Remote Cache for fast CI builds
  • Use Changesets for publishing packages to npm