Monorepo cheatsheet.

Layout

myrepo/
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.base.json
├── turbo.json
├── packages/
   ├── core/
      ├── package.json
      ├── tsconfig.json
      └── src/
   ├── web/
   └── api/
└── tooling/
    └── tsconfig/

pnpm-workspace.yaml

packages:
  - "packages/*"
  - "apps/*"
  - "tooling/*"
pnpm install              # installs all, hoists shared deps
pnpm -r build             # run in all packages
pnpm -F web build         # filter to one
pnpm -F core --filter web run dev

Root package.json

{
  "name": "myrepo",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "typecheck": "turbo run typecheck"
  },
  "devDependencies": {
    "turbo": "^2",
    "typescript": "^5"
  }
}

Workspace dependency

// packages/web/package.json
{
  "dependencies": {
    "@myrepo/core": "workspace:*"
  }
}

pnpm install symlinks @myrepo/core to packages/core.

tsconfig.base.json

{
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "resolveJsonModule": true,
    "noUncheckedIndexedAccess": true,
    "lib": ["ES2023"]
  }
}

Per-package tsconfig

// packages/core/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"]
}
// packages/web/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "references": [
    { "path": "../core" }
  ],
  "include": ["src/**/*"]
}

composite: true enables project references.

Root tsconfig for build

// tsconfig.json
{
  "files": [],
  "references": [
    { "path": "packages/core" },
    { "path": "packages/web" }
  ]
}
tsc --build               # builds all in topological order
tsc --build --watch

turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {},
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"]
    },
    "typecheck": {
      "dependsOn": ["^build"]
    }
  }
}

^build means “build dependencies first.”

turbo run build           # runs build in all packages, in dep order
turbo run build --filter=web
turbo run build --filter=...web   # all deps of web

Shared config packages

tooling/
├── eslint-config/
   ├── package.json
   └── index.js
├── tsconfig/
   ├── package.json
   ├── base.json
   ├── node.json
   └── react.json
└── prettier-config/
// tooling/tsconfig/package.json
{
  "name": "@myrepo/tsconfig",
  "files": ["*.json"]
}
// packages/web/tsconfig.json
{ "extends": "@myrepo/tsconfig/react.json" }

Internal package types

Two strategies:

A: build per-package — each package compiles to dist/, exports via "main"/"types".

{
  "name": "@myrepo/core",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": { ".": "./dist/index.js" }
}

B: source imports — workspace consumers import from src/, type-check together.

{
  "name": "@myrepo/core",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": { ".": "./src/index.ts" }
}

B is faster for dev (no build step) but requires consumers to transpile through node_modules.

Path aliases at root

Avoid paths in monorepos; use workspace deps instead. Cleaner and works with all tools.

CI

- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
  with:
    cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
- run: pnpm run lint
- run: pnpm run test
- run: pnpm run build

Versioning (changesets)

pnpm add -Dw @changesets/cli
npx changeset init

# Create a changeset
npx changeset

# Version bump and changelog
npx changeset version

# Publish
npx changeset publish

Common mistakes

  • Forgetting composite: true — project refs don’t work.
  • Circular project refs — build hangs / fails.
  • Using paths instead of workspace deps — confusing for tools.
  • Per-package node_modules (npm) — slow, duplicated. Use pnpm.
  • Building everything every time — turbo cache fixes that.

Read this next

If you want my monorepo template (pnpm + turbo + tsconfig packages), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .