engineering
Monorepo Setup Guide
Scaffold and configure monorepos using Turborepo or Nx. Covers workspace structure, package organization, build caching, task pipelines, dependency management, and incremental adoption strategies.
monorepoturboreponxworkspacebuild-cachetask-pipelinepnpm
Works well with agents
Works well with skills
$ npx skills add The-AI-Directory-Company/(…) --skill monorepo-setup-guideSKILL.md
Markdown
| 1 | |
| 2 | # Monorepo Setup Guide |
| 3 | |
| 4 | ## Before you start |
| 5 | |
| 6 | Gather the following from the user: |
| 7 | |
| 8 | 1. **Which tool?** (Turborepo, Nx, or help deciding) |
| 9 | 2. **What packages will exist?** (Apps, shared libraries, configs) |
| 10 | 3. **Package manager?** (pnpm, npm, yarn — pnpm recommended for monorepos) |
| 11 | 4. **What language/framework?** (TypeScript, React, Next.js, Node.js, mixed) |
| 12 | 5. **Existing repo or greenfield?** (Migrating from multi-repo or starting fresh) |
| 13 | 6. **Team size?** (Affects caching and CI strategy) |
| 14 | |
| 15 | If the user says "set up a monorepo," push back: "What packages do you need? I need to know the apps, shared libraries, and your package manager to design the workspace structure." |
| 16 | |
| 17 | **Turborepo vs Nx decision guide:** |
| 18 | - Turborepo: Simpler mental model, zero-config caching, good for TypeScript/JS monorepos under 20 packages. |
| 19 | - Nx: More features (generators, affected commands, module boundary rules), better for large monorepos (20+ packages) or polyglot stacks. |
| 20 | |
| 21 | ## Procedure |
| 22 | |
| 23 | ### Step 1: Define the workspace structure |
| 24 | |
| 25 | ``` |
| 26 | monorepo/ |
| 27 | apps/ |
| 28 | web/ # Next.js frontend |
| 29 | api/ # Express/Fastify backend |
| 30 | mobile/ # React Native app |
| 31 | packages/ |
| 32 | ui/ # Shared component library |
| 33 | config-eslint/ # Shared ESLint config |
| 34 | config-typescript/ # Shared tsconfig |
| 35 | shared-utils/ # Shared utility functions |
| 36 | database/ # Database client and schema |
| 37 | tooling/ |
| 38 | scripts/ # Build and maintenance scripts |
| 39 | turbo.json # or nx.json |
| 40 | package.json # Root workspace config |
| 41 | pnpm-workspace.yaml # Workspace package globs |
| 42 | ``` |
| 43 | |
| 44 | Rules for package organization: |
| 45 | - `apps/` contains deployable applications. Each has its own build output. |
| 46 | - `packages/` contains shared libraries consumed by apps or other packages. |
| 47 | - Config packages (`config-*`) export shared tool configurations. |
| 48 | - Every package has its own `package.json` with a `name` field using a scope: `@repo/ui`. |
| 49 | |
| 50 | ### Step 2: Configure the workspace root |
| 51 | |
| 52 | Create `pnpm-workspace.yaml` listing `apps/*`, `packages/*`, and `tooling/*`. Root `package.json` should be `private: true`, define scripts that delegate to turbo (`turbo build`, `turbo dev`, etc.), pin `packageManager` version, and install only workspace-level tools (turbo) as devDependencies. All other dependencies go in the package that uses them. |
| 53 | |
| 54 | ### Step 3: Configure the task pipeline |
| 55 | |
| 56 | Define tasks in `turbo.json` (or `nx.json` with `targetDefaults`). Essential task definitions: |
| 57 | |
| 58 | - **build:** `dependsOn: ["^build"]`, `outputs: ["dist/**", ".next/**"]` |
| 59 | - **dev:** `cache: false`, `persistent: true` |
| 60 | - **lint:** `dependsOn: ["^build"]` |
| 61 | - **test:** `dependsOn: ["build"]` |
| 62 | - **clean:** `cache: false` |
| 63 | |
| 64 | Key pipeline concepts: |
| 65 | - `^build` means "run build in my dependencies first" (topological dependency) |
| 66 | - `dependsOn: ["build"]` means "run my own build first" |
| 67 | - `outputs` defines what gets cached — must include all build artifacts |
| 68 | - `cache: false` for dev servers and clean commands |
| 69 | - `persistent: true` for long-running dev servers |
| 70 | |
| 71 | ### Step 4: Set up internal package references |
| 72 | |
| 73 | Each package that depends on another workspace package: |
| 74 | |
| 75 | Each shared package needs a scoped `name` (`@repo/ui`), `main`, `types`, and `exports` fields pointing to source entry points. Consumer packages reference them with `"@repo/ui": "workspace:*"` in dependencies. The `workspace:*` protocol tells the package manager to resolve from the workspace, not the registry. |
| 76 | |
| 77 | ### Step 5: Configure shared TypeScript |
| 78 | |
| 79 | Create a `packages/config-typescript/base.json` with strict settings: `strict: true`, `target: ES2022`, `module: ESNext`, `moduleResolution: bundler`, `declaration: true`, `isolatedModules: true`. Each package extends it with `"extends": "@repo/config-typescript/base.json"` and adds its own `outDir` and `include` paths. Different packages override as needed (React packages add `jsx`, Node packages adjust `module`). |
| 80 | |
| 81 | ### Step 6: Enable remote caching |
| 82 | |
| 83 | **Turborepo:** `npx turbo login && npx turbo link` to connect Vercel Remote Cache. **Nx:** `npx nx connect` for Nx Cloud. Both support self-hosted alternatives. Remote caching shares build artifacts across developers and CI — expected impact is 40-70% CI time reduction after warm cache. |
| 84 | |
| 85 | ### Step 7: Configure CI for monorepos |
| 86 | |
| 87 | Use filtering to only build/test affected packages. Turborepo: `turbo build --filter=...[origin/main]`. Nx: `nx affected --target=build --base=origin/main`. CI pipeline should restore remote cache, run affected commands, and only deploy apps whose build output changed. |
| 88 | |
| 89 | ## Quality checklist |
| 90 | |
| 91 | Before delivering the monorepo setup, verify: |
| 92 | |
| 93 | - [ ] Every package has a scoped `name` in its package.json |
| 94 | - [ ] `pnpm-workspace.yaml` (or equivalent) lists all package directories |
| 95 | - [ ] Task pipeline defines `dependsOn` with correct topological order |
| 96 | - [ ] Build `outputs` are specified so caching works correctly |
| 97 | - [ ] Internal packages use `workspace:*` protocol for references |
| 98 | - [ ] TypeScript configs extend a shared base config |
| 99 | - [ ] Dev command is marked `cache: false` and `persistent: true` |
| 100 | - [ ] CI uses affected/filter commands, not full rebuild |
| 101 | |
| 102 | ## Common mistakes |
| 103 | |
| 104 | - **Missing `outputs` in task config.** If `outputs` is empty or wrong, the cache stores nothing. Builds re-run every time despite "cache hit" messages. |
| 105 | - **Circular dependencies between packages.** Package A imports from B, B imports from A. This breaks topological builds. Refactor shared code into a third package. |
| 106 | - **Installing dependencies at the root.** Putting `react` in the root `package.json` makes all packages implicitly depend on it. Install dependencies in the package that uses them. |
| 107 | - **Not using `workspace:*` protocol.** Referencing internal packages by version (`"@repo/ui": "^1.0.0"`) causes the package manager to look in the registry instead of the workspace. |
| 108 | - **Skipping remote cache setup.** Without remote caching, every CI run and every developer rebuilds from scratch. This is the single biggest monorepo performance win. |
| 109 | - **One tsconfig for everything.** Different packages need different settings (React needs JSX, Node packages do not). Use a shared base config that each package extends with overrides. |
| 110 |