nx-workspace-patterns
npx machina-cli add skill wshobson/agents/nx-workspace-patterns --openclawFiles (1)
SKILL.md
10.8 KB
Nx Workspace Patterns
Production patterns for Nx monorepo management.
When to Use This Skill
- Setting up new Nx workspaces
- Configuring project boundaries
- Optimizing CI with affected commands
- Implementing remote caching
- Managing dependencies between projects
- Migrating to Nx
Core Concepts
1. Nx Architecture
workspace/
├── apps/ # Deployable applications
│ ├── web/
│ └── api/
├── libs/ # Shared libraries
│ ├── shared/
│ │ ├── ui/
│ │ └── utils/
│ └── feature/
│ ├── auth/
│ └── dashboard/
├── tools/ # Custom executors/generators
├── nx.json # Nx configuration
└── workspace.json # Project configuration
2. Library Types
| Type | Purpose | Example |
|---|---|---|
| feature | Smart components, business logic | feature-auth |
| ui | Presentational components | ui-buttons |
| data-access | API calls, state management | data-access-users |
| util | Pure functions, helpers | util-formatting |
| shell | App bootstrapping | shell-web |
Templates
Template 1: nx.json Configuration
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"npmScope": "myorg",
"affected": {
"defaultBase": "main"
},
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": [
"build",
"lint",
"test",
"e2e",
"build-storybook"
],
"parallel": 3
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"cache": true
},
"lint": {
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
"cache": true
},
"e2e": {
"inputs": ["default", "^production"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.eslintrc.json"
],
"sharedGlobals": [
"{workspaceRoot}/babel.config.json",
"{workspaceRoot}/tsconfig.base.json"
]
},
"generators": {
"@nx/react": {
"application": {
"style": "css",
"linter": "eslint",
"bundler": "webpack"
},
"library": {
"style": "css",
"linter": "eslint"
},
"component": {
"style": "css"
}
}
}
}
Template 2: Project Configuration
// apps/web/project.json
{
"name": "web",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/web/src",
"projectType": "application",
"tags": ["type:app", "scope:web"],
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/web",
"index": "apps/web/src/index.html",
"main": "apps/web/src/main.tsx",
"tsConfig": "apps/web/tsconfig.app.json",
"assets": ["apps/web/src/assets"],
"styles": ["apps/web/src/styles.css"]
},
"configurations": {
"development": {
"extractLicenses": false,
"optimization": false,
"sourceMap": true
},
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractLicenses": true
}
}
},
"serve": {
"executor": "@nx/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "web:build"
},
"configurations": {
"development": {
"buildTarget": "web:build:development"
},
"production": {
"buildTarget": "web:build:production"
}
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/web/jest.config.ts",
"passWithNoTests": true
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/web/**/*.{ts,tsx,js,jsx}"]
}
}
}
}
Template 3: Module Boundary Rules
// .eslintrc.json
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": [
"type:feature",
"type:ui",
"type:data-access",
"type:util"
]
},
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": [
"type:ui",
"type:data-access",
"type:util"
]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:ui", "type:util"]
},
{
"sourceTag": "type:data-access",
"onlyDependOnLibsWithTags": ["type:data-access", "type:util"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
},
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:web", "scope:shared"]
},
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:api", "scope:shared"]
},
{
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": ["scope:shared"]
}
]
}
]
}
}
]
}
Template 4: Custom Generator
// tools/generators/feature-lib/index.ts
import {
Tree,
formatFiles,
generateFiles,
joinPathFragments,
names,
readProjectConfiguration,
} from "@nx/devkit";
import { libraryGenerator } from "@nx/react";
interface FeatureLibraryGeneratorSchema {
name: string;
scope: string;
directory?: string;
}
export default async function featureLibraryGenerator(
tree: Tree,
options: FeatureLibraryGeneratorSchema,
) {
const { name, scope, directory } = options;
const projectDirectory = directory
? `${directory}/${name}`
: `libs/${scope}/feature-${name}`;
// Generate base library
await libraryGenerator(tree, {
name: `feature-${name}`,
directory: projectDirectory,
tags: `type:feature,scope:${scope}`,
style: "css",
skipTsConfig: false,
skipFormat: true,
unitTestRunner: "jest",
linter: "eslint",
});
// Add custom files
const projectConfig = readProjectConfiguration(
tree,
`${scope}-feature-${name}`,
);
const projectNames = names(name);
generateFiles(
tree,
joinPathFragments(__dirname, "files"),
projectConfig.sourceRoot,
{
...projectNames,
scope,
tmpl: "",
},
);
await formatFiles(tree);
}
Template 5: CI Configuration with Affected
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Derive SHAs for affected commands
uses: nrwl/nx-set-shas@v4
- name: Run affected lint
run: npx nx affected -t lint --parallel=3
- name: Run affected test
run: npx nx affected -t test --parallel=3 --configuration=ci
- name: Run affected build
run: npx nx affected -t build --parallel=3
- name: Run affected e2e
run: npx nx affected -t e2e --parallel=1
Template 6: Remote Caching Setup
// nx.json with Nx Cloud
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"],
"accessToken": "your-nx-cloud-token",
"parallel": 3,
"cacheDirectory": ".nx/cache"
}
}
},
"nxCloudAccessToken": "your-nx-cloud-token"
}
// Self-hosted cache with S3
{
"tasksRunnerOptions": {
"default": {
"runner": "@nx-aws-cache/nx-aws-cache",
"options": {
"cacheableOperations": ["build", "lint", "test"],
"awsRegion": "us-east-1",
"awsBucket": "my-nx-cache-bucket",
"awsProfile": "default"
}
}
}
}
Common Commands
# Generate new library
nx g @nx/react:lib feature-auth --directory=libs/web --tags=type:feature,scope:web
# Run affected tests
nx affected -t test --base=main
# View dependency graph
nx graph
# Run specific project
nx build web --configuration=production
# Reset cache
nx reset
# Run migrations
nx migrate latest
nx migrate --run-migrations
Best Practices
Do's
- Use tags consistently - Enforce with module boundaries
- Enable caching early - Significant CI savings
- Keep libs focused - Single responsibility
- Use generators - Ensure consistency
- Document boundaries - Help new developers
Don'ts
- Don't create circular deps - Graph should be acyclic
- Don't skip affected - Test only what changed
- Don't ignore boundaries - Tech debt accumulates
- Don't over-granularize - Balance lib count
Resources
Source
git clone https://github.com/wshobson/agents/blob/main/plugins/developer-essentials/skills/nx-workspace-patterns/SKILL.mdView on GitHub Overview
Provides production patterns for Nx monorepos, guiding workspace setup, project boundaries, caching, and affected commands. It covers structure, library types, and template configurations to scale CI and migrations. Use it to organize apps/libs, optimize builds, and migrate legacy repos to Nx.
How This Skill Works
It introduces the Nx architecture (workspace/, apps/, libs/, tools/ with nx.json and workspace.json) and defines library types (feature, ui, data-access, util, shell). It then demonstrates templates for configuration (nx.json and project.json) including cacheableOperations, targetDefaults, and namedInputs to enable efficient caching and incremental builds as projects evolve.
When to Use It
- Setting up new Nx workspaces
- Configuring project boundaries
- Optimizing CI with affected commands
- Implementing remote caching
- Migrating to Nx
Quick Start
- Step 1: Bootstrap an Nx workspace (e.g., npx create-nx-workspace) and establish apps/ and libs/ structure as demonstrated.
- Step 2: Add nx.json and a sample project.json (Template 2) with cacheableOperations, targetDefaults, and namedInputs.
- Step 3: Enable and use nx affected commands in CI and enable remote caching to accelerate builds.
Best Practices
- Define clear library boundaries using Feature, Ui, Data-access, Util, and Shell types to organize code.
- Use nx.json and targetDefaults to set cacheable operations and consistent caching behavior.
- Leverage affected commands in CI to run only impacted builds/tests.
- Enable remote caching to share build outputs across environments.
- Plan migrations to Nx incrementally, migrating projects and dependencies with well-defined boundaries.
Example Use Cases
- Structure an Nx workspace with apps/web, apps/api, and libs/shared containing ui/ and utils/.
- Create library types like feature-auth, ui-buttons, data-access-users, util-formatting, and shell-web.
- Configure nx.json with cacheableOperations including build, lint, test, e2e, and build-storybook, plus parallel execution.
- Define targetDefaults and namedInputs to optimize caching and incremental builds across projects.
- Migrate an existing repo to Nx by introducing nx.json and project.json configurations and gradually moving code into apps/libs.
Frequently Asked Questions
Add this skill to your agents