Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Configure and optimize Turborepo monorepo build pipelines with correct task structure, caching, and CI setup.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/best-practices/structure.md
1# Repository Structure23Detailed guidance on structuring a Turborepo monorepo.45## Workspace Configuration67### pnpm (Recommended)89```yaml10# pnpm-workspace.yaml11packages:12- "apps/*"13- "packages/*"14```1516### npm/yarn/bun1718```json19// package.json20{21"workspaces": ["apps/*", "packages/*"]22}23```2425## Root package.json2627```json28{29"name": "my-monorepo",30"private": true,31"packageManager": "[email protected]",32"scripts": {33"build": "turbo run build",34"dev": "turbo run dev",35"lint": "turbo run lint",36"test": "turbo run test"37},38"devDependencies": {39"turbo": "latest"40}41}42```4344Key points:4546- `private: true` - Prevents accidental publishing47- `packageManager` - Enforces consistent package manager version48- **Scripts only delegate to `turbo run`** - No actual build logic here!49- Minimal devDependencies (just turbo and repo tools)5051## Always Prefer Package Tasks5253**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**5455```json56// packages/web/package.json57{58"scripts": {59"build": "next build",60"lint": "eslint .",61"test": "vitest",62"typecheck": "tsc --noEmit"63}64}6566// packages/api/package.json67{68"scripts": {69"build": "tsc",70"lint": "eslint .",71"test": "vitest",72"typecheck": "tsc --noEmit"73}74}75```7677Package tasks enable Turborepo to:78791. **Parallelize** - Run `web#lint` and `api#lint` simultaneously802. **Cache individually** - Each package's task output is cached separately813. **Filter precisely** - Run `turbo run test --filter=web` for just one package8283**Root Tasks are a fallback** for tasks that truly cannot run per-package:8485```json86// AVOID unless necessary - sequential, not parallelized, can't filter87{88"scripts": {89"lint": "eslint apps/web && eslint apps/api && eslint packages/ui"90}91}92```9394## Root turbo.json9596```json97{98"$schema": "https://v2-9-12.turborepo.dev/schema.json",99"tasks": {100"build": {101"dependsOn": ["^build"],102"outputs": ["dist/**", ".next/**", "!.next/cache/**"]103},104"lint": {},105"test": {106"dependsOn": ["build"]107},108"dev": {109"cache": false,110"persistent": true111}112}113}114```115116With `futureFlags.globalConfiguration`, global settings move under a `global` key:117118```json119{120"$schema": "https://v2-9-12.turborepo.dev/schema.json",121"futureFlags": { "globalConfiguration": true },122"global": {123"inputs": ["tsconfig.json"],124"env": ["CI"]125},126"tasks": {127"build": {128"dependsOn": ["^build"],129"outputs": ["dist/**", ".next/**", "!.next/cache/**"]130},131"lint": {},132"test": {133"dependsOn": ["build"]134},135"dev": {136"cache": false,137"persistent": true138}139}140}141```142143## Directory Organization144145### Grouping Packages146147You can group packages by adding more workspace paths:148149```yaml150# pnpm-workspace.yaml151packages:152- "apps/*"153- "packages/*"154- "packages/config/*" # Grouped configs155- "packages/features/*" # Feature packages156```157158This allows:159160```161packages/162├── ui/163├── utils/164├── config/165│ ├── eslint/166│ ├── typescript/167│ └── tailwind/168└── features/169├── auth/170└── payments/171```172173### What NOT to Do174175```yaml176# BAD: Nested wildcards cause ambiguous behavior177packages:178- "packages/**" # Don't do this!179```180181## Package Anatomy182183### Minimum Required Files184185```186packages/ui/187├── package.json # Required: Makes it a package188├── src/ # Source code189│ └── button.tsx190└── tsconfig.json # TypeScript config (if using TS)191```192193### package.json Requirements194195```json196{197"name": "@repo/ui", // Unique, namespaced name198"version": "0.0.0", // Version (can be 0.0.0 for internal)199"private": true, // Prevents accidental publishing200"exports": {201// Entry points202"./button": "./src/button.tsx"203}204}205```206207## TypeScript Configuration208209### Shared Base Config210211Create a shared TypeScript config package:212213```214packages/215└── typescript-config/216├── package.json217├── base.json218├── nextjs.json219└── library.json220```221222```json223// packages/typescript-config/base.json224{225"compilerOptions": {226"strict": true,227"esModuleInterop": true,228"skipLibCheck": true,229"moduleResolution": "bundler",230"module": "ESNext",231"target": "ES2022"232}233}234```235236### Extending in Packages237238```json239// packages/ui/tsconfig.json240{241"extends": "@repo/typescript-config/library.json",242"compilerOptions": {243"outDir": "dist",244"rootDir": "src"245},246"include": ["src"],247"exclude": ["node_modules", "dist"]248}249```250251### No Root tsconfig.json252253You likely don't need a `tsconfig.json` in the workspace root. Each package should have its own config extending from the shared config package.254255## ESLint Configuration256257### Shared Config Package258259```260packages/261└── eslint-config/262├── package.json263├── base.js264├── next.js265└── library.js266```267268```json269// packages/eslint-config/package.json270{271"name": "@repo/eslint-config",272"exports": {273"./base": "./base.js",274"./next": "./next.js",275"./library": "./library.js"276}277}278```279280### Using in Packages281282```js283// apps/web/.eslintrc.js284module.exports = {285extends: ["@repo/eslint-config/next"]286};287```288289## Lockfile290291A lockfile is **required** for:292293- Reproducible builds294- Turborepo to understand package dependencies295- Cache correctness296297Without a lockfile, you'll see unpredictable behavior.298