Advanced monorepo architecture covering Nx, Turborepo, and Lerna tooling, workspace management patterns, build caching strategies, dependency graph optimization, task pipeline design, and migration planning for large-scale codebases.
Use when the user asks about monorepo architect, monorepo architect best practices, or needs guidance on monorepo architect implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
Installs into .claude/skills of the current project.
Are you the author of Monorepo Architect?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ferroxlabs-monorepo-architect)
---
name: monorepo-architect
description: |
Advanced monorepo architecture covering Nx, Turborepo, and Lerna tooling, workspace management patterns, build caching strategies, dependency graph optimization, task pipeline design, and migration planning for large-scale codebases.
Use when the user asks about monorepo architect, monorepo architect best practices, or needs guidance on monorepo architect implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
license: Apache-2.0
metadata:
author: foundry-skills
version: "1.0.0"
tags: "best-practices architecture guide"
category: "software-engineering"
subcategory: "architecture-design"
depends: ""
disclaimer: "none"
difficulty: "intermediate"
---
# Monorepo Architect
You are a senior monorepo architect who designs and optimizes large-scale monorepo systems. Go beyond basic setup to tackle the hard problems: build caching that actually works, dependency graphs that stay clean as the org grows, task pipelines that maximize parallelism, and migration strategies that don't require a big-bang switchover.
## Tooling Selection Framework
### Decision Matrix: Nx vs Turborepo vs Lerna vs Bazel
| Factor | Nx | Turborepo | Lerna (v7+) | Bazel |
|--------|-----|-----------|-------------|-------|
| **Primary strength** | Full-featured orchestration + code generation | Speed and simplicity | Publishing workflows | Hermetic multi-language builds |
| **Remote caching** | Nx Cloud (paid/self-host) | Vercel Remote Cache | None built-in | Remote Execution API |
| **Affected detection** | Project graph + file hashing | Content hashing | Changed since ref | Action graph + content hash |
| **Code generation** | Yes (generators + plugins) | No | No | No |
| **Multi-language** | Plugins (Go, Rust, Java) | JS/TS only | JS/TS only | Native (any language) |
| **Incremental adoption** | Yes (add to existing repo) | Yes (add to existing repo) | Yes | Hard (requires BUILD files) |
| **Learning curve** | Medium-High | Low | Low | Very High |
| **Best for** | 10-500 packages, JS/TS-heavy | 5-100 packages, speed-first | Publishing many npm packages | 100+ packages, multi-language |
| **CI time savings** | 60-90% with caching | 60-85% with caching | Minimal | 70-95% with remote execution |
### When to Choose Each
```
START HERE: What is your primary concern?
├── "We need code generation and architectural enforcement"
│ └── Nx (generators, module boundaries, project graph)
│
├── "We just want fast builds with minimal config"
│ └── Turborepo (near-zero config, great defaults)
│
├── "We publish many npm packages"
│ └── Lerna + Nx (Lerna for versioning, Nx for orchestration)
│
├── "We have multiple languages (Go, Java, Rust, JS)"
│ └── Bazel (hermetic builds, any language)
│ NOTE: Only if you can afford 2-4 weeks of setup
│
└── "We just need workspace dependency linking"
└── pnpm workspaces (or npm/yarn workspaces)
Add Nx or Turborepo later when you need caching
```
## Workspace Architecture Patterns
### Package Categorization
Organize packages into clear categories. This is the single most important architectural decision.
```
monorepo/
├── apps/ # Deployable applications
│ ├── web-app/
│ ├── mobile-app/
│ └── api-server/
├── packages/ # Shared libraries
│ ├── ui/ # Shared UI components
│ ├── utils/ # Shared utilities
│ ├── config/ # Shared configuration
│ └── types/ # Shared TypeScript types
├── tools/ # Build tools, scripts, generators
│ ├── eslint-config/
│ ├── tsconfig/
│ └── scripts/
└── services/ # Backend microservices
├── auth-service/
├── billing-service/
└── notification-service/
```
### Dependency Rules (Module Boundaries)
Enforce these rules or your dependency graph becomes a tangled mess within 6 months.
```
DEPENDENCY DIRECTION (allowed):
apps -> packages -> (nothing or other packages)
apps -> services (via API, not import)
services -> packages
tools -> (nothing)
FORBIDDEN:
packages -> apps (library depends on app)
circular dependencies (A -> B -> A)
apps -> apps (app imports from another app)
deep cross-category (ui -> billing-service)
```
#### Nx Module Boundary Enforcement
```json
// .eslintrc.json
{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{ "sourceTag": "type:app", "onlyDependOnLibsWithTags": ["type:lib", "type:util"] },
{ "sourceTag": "type:lib", "onlyDependOnLibsWithTags": ["type:lib", "type:util"] },
{ "sourceTag": "type:util", "onlyDependOnLibsWithTags": ["type:util"] },
{ "sourceTag": "scope:billing", "onlyDependOnLibsWithTags": ["scope:billing", "scope:shared"] },
{ "sourceTag": "scope:auth", "onlyDependOnLibsWithTags": ["scope:auth", "scope:shared"] }
]
}
]
}
}
```
## Build Caching Deep Dive
### How Content-Based Hashing Works
```
INPUT HASH = hash(
source files (content of all files in the package)
+ dependencies (hashes of all dependency packages)
+ environment (Node version, OS, env vars you declare)
+ task config (the command being run, its arguments)
)
If INPUT HASH matches a cached entry -> skip execution, replay outputs
If no match -> execute task, store outputs keyed by INPUT HASH
```
### Cache Configuration (Turborepo)
```json
// turbo.json
{
"$schema": "[reference URL]",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "tsconfig.json", "package.json"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"cache": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "test/**", "jest.config.*"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"inputs": ["src/**", ".eslintrc.*", "tsconfig.json"],
"outputs": [],
"cache": true
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
}
}
}
```
### Cache Poisoning Prevention
Common causes of cache misses that should be hits:
| Problem | Symptom | Fix |
|---------|---------|-----|
| Timestamps in output | Cache never hits | Remove timestamps or make them deterministic |
| Absolute paths in output | Cache misses on different machines | Use relative paths |
| Undeclared env vars | Inconsistent results | Explicitly declare all env vars in `globalEnv` |
| Non-deterministic builds | Intermittent misses | Fix build to be deterministic (sort imports, etc.) |
| OS-specific outputs | Cross-platform misses | Separate cache per OS or normalize outputs |
### Remote Cache Setup
```shell
# Turborepo + custom S3 remote cache
# Use ducktors/turborepo-remote-cache for self-hosted
docker run -p 3000:3000 \
-e STORAGE_PROVIDER=s3 \
-e S3_ACCESS_KEY=xxx \
-e S3_SECRET_KEY=xxx \
-e S3_BUCKET=turbo-cache \
ducktors/turborepo-remote-cache
# Point Turborepo at it
# .turbo/config.json
{
"teamId": "my-team",
"apiUrl": "[reference URL]"
}
```
## Dependency Graph Optimization
### Detecting and Breaking Circular Dependencies
```shell
# Nx: Visualize the dependency graph
npx nx graph
# Nx: Find circular dependencies
npx nx lint --rule '@nx/enforce-module-boundaries'
# Madge: Language-agnostic circular dependency detection
npx madge --circular --extensions ts src/
```
### Strategies for Breaking Cycles
1. **Extract shared interface**: Move the shared types to a separate `types` package
2. **Dependency inversion**: Depend on abstractions, not implementations
3. **Event-based decoupling**: Replace direct imports with event emission
4. **Merge packages**: If two packages are always changed together, they are one package
### Graph Depth Optimization
Deep dependency chains serialize your build. Aim for wide, shallow graphs.
```
BAD (depth 5, serialized):
app -> feature -> domain -> utils -> types
Build time: sum of all build times
GOOD (depth 2, parallelized):
app -> feature-a (depends on: types, utils)
-> feature-b (depends on: types, domain)
-> feature-c (depends on: utils)
Build time: max of parallel build times
```
## Task Pipeline Design
### Parallelism Maximization
```json
// Nx: target defaults in nx.json
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"], // Wait for deps to build first
"inputs": ["production"],
"cache": true
},
"test": {
"dependsOn": ["build"], // Build self first, then test
"inputs": ["default", "^production"],
"cache": true
},
"lint": {
"dependsOn": [], // No dependencies - runs immediately
"inputs": ["default"],
"cache": true
},
"e2e": {
"dependsOn": ["build"],
"cache": true
}
},
"parallel": 4
}
```
### Task Orchestration Anti-Patterns
| Anti-Pattern | Impact | Fix |
|-------------|--------|-----|
| `lint` depends on `build` | Lint waits for build unnecessarily | Remove dependency (lint source, not output) |
| `test` depends on `^test` | Tests wait for dependency tests | Depend on `^build` only |
| Everything depends on `^build` | Over-serialized | Only add `^build` if you import built output |
| No `inputs` specified | Cache invalidates on any file change | Specify exactly which files affect the task |
## Migration Strategies
### Polyrepo to Monorepo Migration
```
Phase 1: Preparation (1-2 weeks)
├── Set up monorepo skeleton with tooling
├── Configure CI/CD for monorepo
├── Document package naming conventions
└── Set up remote caching
Phase 2: Pilot (1-2 weeks)
├── Move 2-3 related repos in
├── Validate build/test/deploy still works
├── Measure CI time improvement
└── Document gotchas
Phase 3: Incremental Migration (2-8 weeks)
├── Move repos in priority order (most shared first)
├── Keep old repos as read-only mirrors temporarily
├── Update CI/CD and deployment pipelines
└── Redirect old repo links
Phase 4: Cleanup (1 week)
├── Archive old repositories
├── Update documentation
└── Remove temporary mirrors
```
### Preserving Git History During Migration
```shell
# In the monorepo, add the old repo as a remote
git remote add old-repo [reference URL]
git get old-repo
# Move files to their new location in a subtree
git merge old-repo/main --allow-unrelated-histories --no-commit
# Then move files to apps/old-repo/ or packages/old-repo/
git mv src apps/old-repo/src
git mv package.json apps/old-repo/package.json
git commit -m "migrate: move old-repo into monorepo"
git remote remove old-repo
```
## CI/CD Optimization
### Affected-Only CI
```yaml
# GitHub Actions example with Nx
name: CI
on: [pull_request]
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
get-depth: 0
- uses: nrwl/nx-set-shas@v4
- run: npm ci
- run: npx nx affected -t lint --parallel=3
- run: npx nx affected -t test --parallel=3
- run: npx nx affected -t build --parallel=3
```
### Distributed Task Execution
For very large monorepos (50+ packages), split tasks across multiple CI agents:
```yaml
# Nx Cloud distributed execution
jobs:
agents:
strategy:
matrix:
agent: [1, 2, 3, 4, 5]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx nx-cloud start-agent
orchestrator:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
get-depth: 0
- run: npm ci
- run: npx nx-cloud start-ci-run --distribute-on="5 linux-medium-js"
- run: npx nx affected -t lint test build e2e
- run: npx nx-cloud stop-all-agents
```
## Common Pitfalls
1. **"Let's monorepo everything"**: Not every repo belongs in a monorepo. Repos with completely independent lifecycles, different languages with no shared code, or different security requirements should stay separate.
2. **Ignoring the dependency graph**: Without enforced boundaries, your graph becomes fully connected within a year, and every change triggers a full rebuild.
3. **No remote caching**: Local caching helps developers. Remote caching helps CI and the whole team. Without remote caching, you are leaving 50-80% of the value on the table.
4. **Shared `node_modules` confusion**: Hoisting creates phantom dependencies. Use `pnpm` strict mode or Nx's isolated installs to catch packages that work locally but fail in production.
5. **Monolithic CI config**: One giant CI pipeline for all packages. Use affected detection and per-package deployment triggers instead.
## Scaling Checklist
- [ ] Package categorization defined (apps, packages, tools, services)
- [ ] Module boundary rules enforced via linting
- [ ] Build caching configured with declared inputs/outputs
- [ ] Remote caching operational for CI and team
- [ ] Affected detection working in CI (only test what changed)
- [ ] Dependency graph is acyclic and shallow
- [ ] Task pipelines maximize parallelism
- [ ] Code generators available for new packages
- [ ] CODEOWNERS file maps packages to teams
- [ ] Migration runbook documented for remaining repos
## When to Use
**Use this skill when:**
- Designing or implementing monorepo architect solutions
- Reviewing or improving existing monorepo architect approaches
- Making architectural or implementation decisions about monorepo architect
- Learning monorepo architect patterns and best practices
- Troubleshooting monorepo architect-related issues
**Do NOT use this skill when:**
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
## Output Format
```markdown
# Monorepo Architect Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
```
## Example
**Input:** "Help me implement monorepo architect for a medium-scale production application"
**Output:** A structured analysis covering current state assessment, recommended monorepo architect approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
## Edge Cases
- **Legacy system integration:** When monorepo architect must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- **Scale mismatch:** When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- **Team skill gaps:** When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- **Conflicting requirements:** When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities