// Use when setting up monorepo tooling, optimizing builds, or migrating between tools with Turborepo, Nx, Bazel, Lerna for efficient task running, caching, and code generation.
| name | monorepo-tooling |
| description | Use when setting up monorepo tooling, optimizing builds, or migrating between tools with Turborepo, Nx, Bazel, Lerna for efficient task running, caching, and code generation. |
| allowed-tools | Read,Write,Edit,Bash,Glob,Grep |
This skill provides comprehensive guidance on monorepo build systems, task runners, package managers, and development tools that enable efficient development, building, and testing across multiple packages in a monorepo.
High-performance build system with intelligent caching and task orchestration.
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [
".env",
"tsconfig.json"
],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [
"dist/**",
".next/**",
"build/**"
],
"cache": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"outputs": [],
"cache": true
},
"dev": {
"cache": false,
"persistent": true
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"cache": false
}
},
"globalEnv": [
"NODE_ENV",
"CI"
]
}
{
"remoteCache": {
"enabled": true
}
}
With Vercel:
# Link to Vercel for remote caching
turbo login
turbo link
With custom cache:
{
"remoteCache": {
"enabled": true,
"signature": true,
"preflight": true
}
}
Usage:
# Run build across all packages
turbo run build
# Run with filter
turbo run build --filter=@myorg/web
# Run with dependencies
turbo run build --filter=@myorg/web...
# Force rebuild (skip cache)
turbo run build --force
# Dry run
turbo run build --dry-run
# Prune for deployment
turbo prune --scope=@myorg/web
Extensible build system with powerful code generation and analysis.
{
"extends": "nx/presets/npm.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"test",
"lint"
],
"parallel": 3,
"cacheDirectory": "node_modules/.cache/nx"
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"],
"cache": true
},
"test": {
"inputs": [
"default",
"^production"
],
"cache": true
}
},
"namedInputs": {
"default": [
"{projectRoot}/**/*"
],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts"
]
}
}
{
"name": "web",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/web",
"main": "apps/web/src/main.ts",
"tsConfig": "apps/web/tsconfig.app.json"
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"options": {
"buildTarget": "web:build"
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/web"],
"options": {
"jestConfig": "apps/web/jest.config.ts"
}
}
}
}
{
"nxCloudAccessToken": "YOUR_ACCESS_TOKEN",
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"accessToken": "YOUR_ACCESS_TOKEN"
}
}
}
}
Usage:
# Run target on all projects
nx run-many --target=build --all
# Run on affected projects only
nx affected --target=test --base=main
# View dependency graph
nx graph
# Generate new library
nx generate @nx/js:library my-lib
# Run task on specific project
nx build web
# Clear cache
nx reset
Google's scalable build system for very large monorepos.
workspace(name = "my_workspace")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# Load Node.js rules
http_archive(
name = "build_bazel_rules_nodejs",
sha256 = "...",
urls = ["https://github.com/bazelbuild/rules_nodejs/..."],
)
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories")
node_repositories(
node_version = "18.16.0",
package_manager = "pnpm",
)
# packages/ui/BUILD.bazel
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_npm")
load("@npm//@bazel/typescript:index.bzl", "ts_library")
ts_library(
name = "ui",
srcs = glob(["src/**/*.ts", "src/**/*.tsx"]),
deps = [
"@npm//react",
"@npm//react-dom",
"@npm//@types/react",
],
visibility = ["//visibility:public"],
)
pkg_npm(
name = "ui_pkg",
deps = [":ui"],
package_name = "@myorg/ui",
substitutions = {
"0.0.0-PLACEHOLDER": "{STABLE_VERSION}",
},
)
Usage:
# Build target
bazel build //packages/ui:ui
# Build all targets in package
bazel build //packages/ui/...
# Test target
bazel test //packages/ui:ui_test
# Run target
bazel run //apps/web:serve
# Clean builds
bazel clean
# Query dependency graph
bazel query 'deps(//packages/ui:ui)'
Multi-package repository management and publishing tool.
{
"version": "independent",
"npmClient": "pnpm",
"useWorkspaces": true,
"packages": [
"packages/*",
"apps/*"
],
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish",
"ignoreChanges": [
"**/__tests__/**",
"**/*.md"
]
},
"version": {
"allowBranch": ["main", "next"],
"message": "chore(release): version packages"
},
"bootstrap": {
"npmClientArgs": ["--no-package-lock"]
}
}
}
Usage:
# Bootstrap packages
lerna bootstrap
# Run command in all packages
lerna run build
# Run command in changed packages
lerna run test --since origin/main
# Publish packages
lerna publish
# Publish from git tags
lerna publish from-git
# Version packages
lerna version
# List packages
lerna list
Scalable monorepo manager with strict dependency management.
{
"rushVersion": "5.108.0",
"pnpmVersion": "8.10.0",
"nodeSupportedVersionRange": ">=18.0.0",
"projectFolderMinDepth": 1,
"projectFolderMaxDepth": 2,
"projects": [
{
"packageName": "@myorg/web",
"projectFolder": "apps/web",
"reviewCategory": "production"
},
{
"packageName": "@myorg/ui",
"projectFolder": "packages/ui",
"reviewCategory": "production"
}
]
}
Rush build configuration:
{
"operationSettings": [
{
"operationName": "build",
"outputFolderNames": ["dist", "lib"]
}
]
}
Usage:
# Install dependencies
rush install
# Update dependencies
rush update
# Build all projects
rush build
# Build changed projects
rush rebuild
# Custom commands
rush my-command
# Publish packages
rush publish
Execute tasks across packages in parallel for speed.
Turborepo parallel execution:
# Auto-detects parallelism
turbo run build
# Limit concurrency
turbo run build --concurrency=4
# No limit
turbo run build --concurrency=100
Nx parallel execution:
# Default parallel (3)
nx run-many --target=build --all
# Custom parallel
nx run-many --target=build --all --parallel=5
# Max parallel
nx run-many --target=build --all --parallel=false
PNPM parallel execution:
# Run in all packages (parallel)
pnpm -r run build
# Sequential execution
pnpm -r --workspace-concurrency=1 run build
# Custom concurrency
pnpm -r --workspace-concurrency=4 run build
Define which tasks must complete before others.
{
"pipeline": {
"build": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
},
"deploy": {
"dependsOn": ["build", "test", "lint"]
}
}
}
Dependency types:
^build - Build dependencies first (topological)build - Current package build first["^build", "lint"] - Multiple dependenciesRun tasks on specific packages or changed packages only.
Turborepo filtering:
# Single package
turbo run build --filter=@myorg/web
# Package and dependencies
turbo run build --filter=@myorg/web...
# Package and dependents
turbo run build --filter=...@myorg/web
# Multiple packages
turbo run build --filter=@myorg/web --filter=@myorg/api
# Changed packages
turbo run build --filter=[HEAD^1]
Nx affected:
# Affected by current changes
nx affected --target=build
# Affected between commits
nx affected --target=test --base=main --head=HEAD
# Affected files
nx affected:apps
nx affected:libs
PNPM filtering:
# Single package
pnpm --filter @myorg/web run build
# Package and dependencies
pnpm --filter @myorg/web... run build
# Package and dependents
pnpm --filter ...@myorg/web run build
# Changed packages
pnpm --filter "[main]" run test
Automatically rebuild on file changes.
{
"scripts": {
"dev": "turbo run dev --parallel",
"dev:web": "turbo run dev --filter=@myorg/web..."
}
}
With concurrently:
{
"scripts": {
"dev": "concurrently \"pnpm:dev:*\"",
"dev:web": "pnpm --filter @myorg/web run dev",
"dev:api": "pnpm --filter @myorg/api run dev"
}
}
With Turborepo watch:
# Watch mode for development
turbo run dev --parallel --no-cache
Cache task outputs locally for faster rebuilds.
Turborepo local cache:
{
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**"],
"cache": true
}
}
}
Cache location: node_modules/.cache/turbo
Nx local cache:
{
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["build", "test", "lint"],
"cacheDirectory": "node_modules/.cache/nx"
}
}
}
}
Cache location: node_modules/.cache/nx
Share cache across team members and CI environments.
Turborepo Remote Cache with Vercel:
# Login to Vercel
turbo login
# Link repository
turbo link
# Enable remote caching (automatic)
turbo run build
Nx Cloud:
# Connect to Nx Cloud
nx connect-to-nx-cloud
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"accessToken": "YOUR_TOKEN"
}
}
}
}
Custom Remote Cache:
// turbo-remote-cache.ts
import { createServer } from 'turbo-remote-cache';
createServer({
storage: {
type: 's3',
bucket: 'my-turbo-cache',
region: 'us-east-1'
},
port: 3000
});
Control what invalidates the cache.
Turborepo cache keys:
{
"globalDependencies": [
".env",
"tsconfig.json",
".eslintrc.js"
],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"inputs": [
"src/**/*.ts",
"src/**/*.tsx",
"package.json"
],
"outputs": ["dist/**"]
}
}
}
Nx cache inputs:
{
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/**/*.test.ts"
],
"sharedGlobals": [
"{workspaceRoot}/tsconfig.base.json",
"{workspaceRoot}/.eslintrc.json"
]
},
"targetDefaults": {
"build": {
"inputs": ["production", "^production", "sharedGlobals"]
}
}
}
Cache invalidation:
# Clear all caches
turbo run build --force
# Clear Nx cache
nx reset
# Skip cache for single run
nx build web --skip-nx-cache
Optimize Docker builds in monorepo context.
# Dockerfile for web app
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
# Copy root package files
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY .npmrc ./
# Copy package files for dependencies
COPY packages/ui/package.json ./packages/ui/
COPY packages/utils/package.json ./packages/utils/
COPY apps/web/package.json ./apps/web/
# Install dependencies
RUN corepack enable pnpm && pnpm install --frozen-lockfile
# Build stage
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build with Turbo
RUN pnpm turbo run build --filter=@myorg/web...
# Production stage
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/apps/web/.next ./apps/web/.next
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "apps/web/.next/server.js"]
Create custom code generators for consistency.
// tools/generators/library/index.ts
import {
Tree,
formatFiles,
generateFiles,
joinPathFragments
} from '@nx/devkit';
export interface LibraryGeneratorSchema {
name: string;
directory: string;
}
export default async function (tree: Tree, schema: LibraryGeneratorSchema) {
const projectRoot = joinPathFragments('packages', schema.directory);
generateFiles(
tree,
joinPathFragments(__dirname, 'files'),
projectRoot,
{
...schema,
tmpl: ''
}
);
await formatFiles(tree);
}
Template files:
// tools/generators/library/files/src/index.ts__tmpl__
export function <%= name %>() {
return '<%= name %>';
}
// tools/generators/library/files/package.json__tmpl__
{
"name": "@myorg/<%= name %>",
"version": "0.0.1"
}
Usage:
nx generate @myorg/tools:library --name=my-lib --directory=shared
Use Plop for simpler code generation.
// plopfile.js
export default function (plop) {
plop.setGenerator('package', {
description: 'Create a new package',
prompts: [
{
type: 'input',
name: 'name',
message: 'Package name:'
},
{
type: 'list',
name: 'type',
message: 'Package type:',
choices: ['library', 'app', 'service']
}
],
actions: [
{
type: 'addMany',
destination: 'packages/{{name}}',
templateFiles: 'templates/package/**/*',
base: 'templates/package'
}
]
});
}
Templates:
// templates/package/package.json
{
"name": "@myorg/{{name}}",
"version": "0.0.1",
"type": "{{type}}"
}
Usage:
pnpm plop package
Automate new package creation.
#!/bin/bash
# scripts/create-package.sh
NAME=$1
TYPE=$2
if [ -z "$NAME" ]; then
echo "Usage: create-package.sh <name> <type>"
exit 1
fi
DIR="packages/$NAME"
# Create directory structure
mkdir -p "$DIR/src"
mkdir -p "$DIR/__tests__"
# Create package.json
cat > "$DIR/package.json" << EOF
{
"name": "@myorg/$NAME",
"version": "0.0.1",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"test": "vitest"
}
}
EOF
# Create initial files
echo "export const $NAME = '$NAME';" > "$DIR/src/index.ts"
# Create README
cat > "$DIR/README.md" << EOF
# @myorg/$NAME
Description of $NAME package.
EOF
echo "Created package: @myorg/$NAME"
Maintain consistency with shared templates.
// scripts/new-component.ts
import fs from 'fs';
import path from 'path';
interface ComponentOptions {
name: string;
package: string;
}
function createComponent({ name, package: pkg }: ComponentOptions) {
const dir = path.join('packages', pkg, 'src', 'components', name);
fs.mkdirSync(dir, { recursive: true });
// Component file
fs.writeFileSync(
path.join(dir, `${name}.tsx`),
`import React from 'react';
export interface ${name}Props {
children?: React.ReactNode;
}
export function ${name}({ children }: ${name}Props) {
return <div>{children}</div>;
}
`
);
// Test file
fs.writeFileSync(
path.join(dir, `${name}.test.tsx`),
`import { render } from '@testing-library/react';
import { ${name} } from './${name}';
describe('${name}', () => {
it('renders children', () => {
const { getByText } = render(<${name}>Hello</${name}>);
expect(getByText('Hello')).toBeInTheDocument();
});
});
`
);
// Index file
fs.writeFileSync(
path.join(dir, 'index.ts'),
`export { ${name} } from './${name}';
export type { ${name}Props } from './${name}';
`
);
}
Identify which packages changed based on Git history.
Turborepo:
# Changed since last commit
turbo run build --filter=[HEAD^1]
# Changed in last 3 commits
turbo run build --filter=[HEAD^3]
# Changed between branches
turbo run build --filter=[origin/main...HEAD]
Nx:
# Affected since main branch
nx affected --target=build --base=main
# Affected between specific commits
nx affected --target=test --base=abc123 --head=def456
# Show affected projects
nx affected:apps
nx affected:libs
Understand project relationships for smarter builds.
Visualize with Nx:
# Full dependency graph
nx graph
# Affected dependency graph
nx affected:graph
# Specific project graph
nx graph --focus=web
Query with Nx:
# Show dependencies of project
nx show project web --web
# List all projects
nx show projects
Turborepo graph:
# Generate task graph
turbo run build --graph
# Output to file
turbo run build --graph=graph.html
Only rebuild what's necessary based on changes.
Nx affected strategy:
{
"affected": {
"defaultBase": "main"
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
}
}
Turborepo affected strategy:
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
CI configuration:
# .github/workflows/ci.yml
- name: Build affected
run: |
turbo run build --filter=[origin/main...HEAD]
Build only changed files within packages.
TypeScript incremental builds:
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./dist/.tsbuildinfo"
}
}
Webpack incremental builds:
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
};
Next.js incremental builds:
// next.config.js
module.exports = {
experimental: {
incrementalCacheHandlerPath: './cache-handler.js'
}
};
Fast, disk-efficient package manager with strict dependency model.
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
- 'services/*'
# .npmrc
shared-workspace-lockfile=true
link-workspace-packages=true
prefer-workspace-packages=true
strict-peer-dependencies=false
auto-install-peers=true
save-workspace-protocol=rolling
Key advantages:
Commands:
# Install all workspace dependencies
pnpm install
# Add dependency to package
pnpm --filter @myorg/web add react
# Add workspace dependency
pnpm --filter @myorg/web add @myorg/ui
# Update dependencies
pnpm --filter @myorg/web update react
# Run script in all packages
pnpm -r run build
# Run in changed packages
pnpm --filter "[main]" run test
Yarn's workspace implementation with plugin ecosystem.
Yarn Classic:
{
"workspaces": [
"packages/*",
"apps/*"
]
}
Yarn Berry (v2+):
# .yarnrc.yml
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.6.4.cjs
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
spec: "@yarnpkg/plugin-workspace-tools"
Key advantages:
Commands:
# Install dependencies
yarn install
# Add dependency
yarn workspace @myorg/web add react
# Run script in workspace
yarn workspace @myorg/web run build
# Run in all workspaces
yarn workspaces foreach run build
# Run in changed workspaces (Berry)
yarn workspaces foreach --since=main run test
Native NPM workspace support (v7+).
{
"workspaces": [
"packages/*",
"apps/*"
]
}
Key advantages:
Commands:
# Install dependencies
npm install
# Add dependency to workspace
npm install react --workspace=@myorg/web
# Run script in workspace
npm run build --workspace=@myorg/web
# Run in all workspaces
npm run build --workspaces
# Run in specific workspaces
npm run test --workspaces --if-present
Performance comparison (1000 packages, cold install):
Migration from NPM to PNPM:
# Remove node_modules and package-lock.json
rm -rf node_modules package-lock.json
# Install PNPM
corepack enable pnpm
# Create workspace file
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/*'
- 'apps/*'
EOF
# Install with PNPM
pnpm install
# Update package.json scripts
# Replace "npm" with "pnpm"
Migration from Yarn to PNPM:
# Remove node_modules and yarn.lock
rm -rf node_modules yarn.lock
# Create workspace file (convert from package.json)
cat > pnpm-workspace.yaml << EOF
packages:
- 'packages/*'
- 'apps/*'
EOF
# Install with PNPM
pnpm install --lockfile-only
pnpm install
Leverage local and remote caching for maximum speed.
Implementation:
Share build artifacts across CI runs and developers.
Implementation:
Only run tasks on changed packages.
Implementation:
Design efficient task dependency graphs.
Implementation:
Automate package and component creation.
Implementation:
Commit lock files for reproducible builds.
Implementation:
--frozen-lockfile in CIMaintain clear documentation for all tools.
Implementation:
Track and optimize build times.
Implementation:
Stay current with monorepo tool versions.
Implementation:
Ensure workspace is correctly configured.
Implementation:
Missing significant performance gains.
Solution: Enable local and remote caching, configure outputs correctly.
Inefficient task dependencies and ordering.
Solution: Minimize dependencies, enable parallelism, visualize pipeline.
Not using affected analysis.
Solution: Implement affected commands, configure base branch, test locally.
Wasting CI time on unchanged packages.
Solution: Use affected in CI, configure correctly, monitor savings.
Inconsistent package structure.
Solution: Create generators, document usage, enforce in reviews.
Different team members using different commands.
Solution: Document standard commands, use package.json scripts, code review.
Stale builds from incorrect cache configuration.
Solution: Configure inputs/outputs correctly, test cache behavior, monitor.
Complex task graphs that are hard to maintain.
Solution: Simplify dependencies, regular reviews, documentation.
Using multiple tools that overlap.
Solution: Choose one primary tool, justify additions, regular audits.
Not tracking build performance over time.
Solution: Implement metrics, regular reviews, set budgets, optimize.
Apply monorepo tooling practices when: