TypeScript Monorepo (Turborepo) AI Rules

Rules for Turborepo monorepos: workspace structure, internal-package conventions, TypeScript project references, shared ESLint/Prettier configs, and remote-caching workflow.

TypeScriptTurborepo#monorepo#turborepo#pnpm#typescriptLast updated 2026-05-05
tune

Want to customize this rules file? Open the generator with this stack pre-loaded.

Open in generatorarrow_forward

Save at .cursor/rules/main.mdc

TypeScript Monorepo (Turborepo)

Project context

This is a TypeScript monorepo managed with Turborepo and pnpm workspaces. We have shared packages/* for libraries and apps/* for deployable applications. Each workspace is a real, named package that other workspaces depend on through pnpm's workspace:* protocol.

Stack

  • Turborepo 2+
  • pnpm 9+ with workspaces
  • TypeScript 5+ with project references
  • Shared ESLint, Prettier, and tsconfig configs as internal packages
  • Node.js 20+
  • Optional: Changesets for versioning published packages

Folder structure

.
├── apps/
│   ├── web/                — Next.js app
│   └── api/                — Node service
├── packages/
│   ├── ui/                 — shared React components
│   ├── db/                 — shared Drizzle / Prisma schema and client
│   ├── config/             — shared runtime config
│   ├── eslint-config/      — internal ESLint config (extends from)
│   ├── tsconfig/           — base tsconfigs (extends from)
│   └── types/              — shared types
├── turbo.json
├── pnpm-workspace.yaml
└── package.json

Workspace conventions

  • Every workspace has a package.json with a real name (@repo/ui, @repo/db)
  • Internal dependencies use "workspace:*" — never relative paths
  • One package.json per workspace; never share node_modules across workspaces
  • Don't publish internal packages — keep "private": true

TypeScript project references

  • Every workspace's tsconfig.json extends @repo/tsconfig/base.json
  • Apps reference packages they consume:
{
  "extends": "@repo/tsconfig/nextjs.json",
  "references": [
    { "path": "../../packages/ui" },
    { "path": "../../packages/db" }
  ]
}
  • Each referenced package must have "composite": true in its tsconfig
  • Run tsc --build (not just tsc) at the root to leverage references

Build pipeline (turbo.json)

  • Define tasks: build, lint, typecheck, test, dev
  • Set "dependsOn": ["^build"] so a task waits for upstream packages first
  • Set "outputs" so Turborepo can cache (e.g. ["dist/**", ".next/**"])
  • Use --filter for partial runs: turbo build --filter=@repo/web
  • Enable remote caching: turbo login once, then turbo link

Internal packages

  • A package exposes its public API through package.json's exports field
  • Use TypeScript directly (no build step) for non-published packages — "main": "./src/index.ts", "types": "./src/index.ts". Apps consume via tsx / Next's compiler.
  • For published packages, build to dist/ with tsup or tsc
{
  "name": "@repo/ui",
  "exports": {
    ".": "./src/index.ts",
    "./button": "./src/button.tsx"
  },
  "scripts": { "lint": "eslint .", "typecheck": "tsc --noEmit" }
}

Shared configs

  • @repo/eslint-config exports preset configs (base.js, nextjs.js, node.js)
  • @repo/tsconfig exports base tsconfigs (base.json, nextjs.json, node.json)
  • Apps and packages extend these, never duplicate

Database / schema sharing

  • One @repo/db package owns the schema (Drizzle / Prisma)
  • Apps import the schema and the client from there
  • Migrations live in @repo/db/migrations and are run via a script in that package

Patterns to follow

  • One concern per workspace — UI components, DB schema, and config don't share a package
  • Use Turborepo's caching aggressively — every task should declare its outputs
  • Use --filter in CI to only run jobs for changed workspaces (turbo build --filter='[origin/main]')
  • Lock down engines in the root package.json to enforce Node + pnpm versions

Patterns to avoid

  • Relative imports across workspaces. Always use the package name (@repo/ui) — relative paths break TypeScript references
  • Hoisting hacks. pnpm uses isolated node_modules per workspace; don't fight it
  • Different versions of the same lib in different workspaces. Use pnpm.overrides if you must — but try to avoid drift
  • Shared dist/ between dev and CI. Each task declares its outputs.
  • Skipping ^build deps to "speed up CI." It's faster overall to build properly with caching.

Testing

  • Each workspace has its own test script that turbo runs
  • Vitest for both apps and packages — single runner across the monorepo
  • Use vitest's workspace config to run all packages in one CLI invocation

Tooling

  • pnpm install at the root — installs all workspaces
  • turbo dev — runs every workspace's dev task in parallel
  • turbo build --filter=@repo/web — build one app and its deps
  • turbo lint typecheck test — multi-task at once
  • pnpm changeset — only if publishing to npm

AI behavioral rules

  • Always import internal packages by their workspace name (@repo/ui), never relative
  • When adding a new internal package, register it in the consumer's tsconfig.json references
  • When adding a new task, declare its outputs in turbo.json so Turborepo caches it
  • Use pnpm add <pkg> --filter <workspace> — don't add to root unless it's truly root-level (linting tools, etc.)
  • Don't install the same package in multiple workspaces with mismatched versions — use pnpm.overrides to align
  • Run turbo lint typecheck test from the root before declaring a task done

Frequently asked

How do I use this Turborepo rules file with Cursor?

Pick "Cursor (.cursor/rules/*.mdc)" from the format dropdown above and click Copy. Save it at .cursor/rules/main.mdc in your project root and restart Cursor. The legacy .cursorrules format still works if you're on an older Cursor version — pick that option instead.

Can I use this with Claude Code (CLAUDE.md)?

Yes — pick "Claude Code (CLAUDE.md)" from the format dropdown above and copy. Save the file as CLAUDE.md at your repo root. Claude Code reads it automatically on every session. For monorepos, you can also drop nested CLAUDE.md files in subdirectories — Claude merges them when working in those paths.

Where exactly do I put this file?

It depends on the AI tool. Cursor reads .cursorrules or .cursor/rules/*.mdc at the project root. Claude reads CLAUDE.md at the project root. Copilot reads .github/copilot-instructions.md. The "Save at" path under each format in the dropdown shows the exact location for the format you picked.

Can I customize these Turborepo rules for my project?

Yes — that's what the generator is for. Click "Open in generator" above and the wizard loads with this stack's defaults pre-selected. Toggle on or off the conventions you want, then re-export in your AI tool's format.

Will using this rules file slow down my AI tool?

No. Rules files count toward the model's context window but not toward latency in any noticeable way. The file is loaded once per session, not per token. The library files target 250–400 lines, well within every tool's recommended budget.

Should I commit this file to git?

Yes. The rules file is project documentation that benefits every developer using the AI tool. Commit it. The exception is personal-global settings (e.g. ~/.claude/CLAUDE.md) which are user-scoped and stay out of the repo.

Related stacks