CLAUDE.md

Monorepo Project Guidelines

Overview

This is a monorepo managed with Turborepo/pnpm (or Nx). Multiple packages and apps share code through internal packages.

Tech Stack

  • Monorepo Tool: Turborepo (or Nx)
  • Package Manager: pnpm (with workspaces)
  • Build: Each package has its own build config
  • Shared Config: ESLint, TypeScript, Prettier configs as packages

Project Structure

├── apps/
│   ├── web/                 # Next.js frontend
│   ├── api/                 # Backend service
│   ├── mobile/              # React Native app
│   └── docs/                # Documentation site
├── packages/
│   ├── ui/                  # Shared UI components
│   ├── database/            # Database client & schema
│   ├── utils/               # Shared utilities
│   ├── types/               # Shared TypeScript types
│   ├── config-eslint/       # Shared ESLint config
│   ├── config-typescript/   # Shared tsconfig
│   └── config-tailwind/     # Shared Tailwind config
├── turbo.json               # Turborepo config
├── pnpm-workspace.yaml      # Workspace definition
└── package.json             # Root package.json

Development Commands

# Install all dependencies
pnpm install

# Run all apps in development
pnpm dev

# Run specific app
pnpm dev --filter=web
pnpm dev --filter=api

# Build all packages
pnpm build

# Build specific package and its dependencies
pnpm build --filter=web...

# Run tests across all packages
pnpm test

# Run linting
pnpm lint

# Add dependency to specific package
pnpm add lodash --filter=web

# Add internal package as dependency
pnpm add @repo/ui --filter=web --workspace

Workspace Configuration

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^build"]
    },
    "test": {
      "dependsOn": ["^build"]
    }
  }
}

Internal Packages

Creating a New Package

# Create package directory
mkdir -p packages/new-package/src
cd packages/new-package

# Initialize package.json
cat > package.json << 'EOF'
{
  "name": "@repo/new-package",
  "version": "0.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch"
  }
}
EOF

Using Internal Packages

// apps/web/package.json
{
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*"
  }
}
// apps/web/app/page.tsx
import { Button } from '@repo/ui'
import { formatDate } from '@repo/utils'

Shared Configurations

TypeScript Config

// packages/config-typescript/base.json
{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true
  }
}

// apps/web/tsconfig.json
{
  "extends": "@repo/config-typescript/nextjs.json",
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

ESLint Config

// packages/config-eslint/next.js
module.exports = {
  extends: ['next/core-web-vitals', './base.js'],
  // shared rules
}

// apps/web/.eslintrc.js
module.exports = {
  extends: ['@repo/config-eslint/next'],
}

Best Practices

Package Boundaries

  • Keep packages focused and small
  • Avoid circular dependencies between packages
  • Use @repo/ prefix for internal packages
  • Define clear public APIs (index.ts exports)

Dependency Management

  • Hoist common dependencies to root when possible
  • Pin versions for consistency across packages
  • Use workspace:* for internal dependencies
  • Keep devDependencies at package level when specific

Caching

  • Configure proper outputs in turbo.json
  • Use remote caching in CI (Vercel Remote Cache, Nx Cloud)
  • Don't cache dev tasks
  • Include hash inputs for environment-dependent builds

CI/CD

# .github/workflows/ci.yml
- name: Install dependencies
  run: pnpm install --frozen-lockfile

- name: Build
  run: pnpm build

- name: Test
  run: pnpm test

# Only run affected tasks
- name: Build affected
  run: pnpm turbo build --filter=...[origin/main]

Common Patterns

Shared UI Components

// packages/ui/src/button.tsx
export interface ButtonProps {
  variant?: 'primary' | 'secondary'
  children: React.ReactNode
}

export function Button({ variant = 'primary', children }: ButtonProps) {
  return <button className={styles[variant]}>{children}</button>
}

// packages/ui/src/index.ts
export { Button } from './button'
export type { ButtonProps } from './button'

Shared Database Package

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

export const db = new PrismaClient()
export * from '@prisma/client'

// Used in apps
import { db, User } from '@repo/database'

Troubleshooting

  • Package not found: Run pnpm install after adding workspace dependency
  • Type errors in IDE: Restart TypeScript server after package changes
  • Cache issues: Run pnpm turbo clean to clear Turborepo cache
  • Circular dependency: Check imports with madge --circular