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
json
1{
2 "$schema": "./node_modules/nx/schemas/nx-schema.json",
3 "npmScope": "myorg",
4 "affected": {
5 "defaultBase": "main"
6 },
7 "tasksRunnerOptions": {
8 "default": {
9 "runner": "nx/tasks-runners/default",
10 "options": {
11 "cacheableOperations": [
12 "build",
13 "lint",
14 "test",
15 "e2e",
16 "build-storybook"
17 ],
18 "parallel": 3
19 }
20 }
21 },
22 "targetDefaults": {
23 "build": {
24 "dependsOn": ["^build"],
25 "inputs": ["production", "^production"],
26 "cache": true
27 },
28 "test": {
29 "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
30 "cache": true
31 },
32 "lint": {
33 "inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
34 "cache": true
35 },
36 "e2e": {
37 "inputs": ["default", "^production"],
38 "cache": true
39 }
40 },
41 "namedInputs": {
42 "default": ["{projectRoot}/**/*", "sharedGlobals"],
43 "production": [
44 "default",
45 "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
46 "!{projectRoot}/tsconfig.spec.json",
47 "!{projectRoot}/jest.config.[jt]s",
48 "!{projectRoot}/.eslintrc.json"
49 ],
50 "sharedGlobals": [
51 "{workspaceRoot}/babel.config.json",
52 "{workspaceRoot}/tsconfig.base.json"
53 ]
54 },
55 "generators": {
56 "@nx/react": {
57 "application": {
58 "style": "css",
59 "linter": "eslint",
60 "bundler": "webpack"
61 },
62 "library": {
63 "style": "css",
64 "linter": "eslint"
65 },
66 "component": {
67 "style": "css"
68 }
69 }
70 }
71}
Template 2: Project Configuration
json
1// apps/web/project.json
2{
3 "name": "web",
4 "$schema": "../../node_modules/nx/schemas/project-schema.json",
5 "sourceRoot": "apps/web/src",
6 "projectType": "application",
7 "tags": ["type:app", "scope:web"],
8 "targets": {
9 "build": {
10 "executor": "@nx/webpack:webpack",
11 "outputs": ["{options.outputPath}"],
12 "defaultConfiguration": "production",
13 "options": {
14 "compiler": "babel",
15 "outputPath": "dist/apps/web",
16 "index": "apps/web/src/index.html",
17 "main": "apps/web/src/main.tsx",
18 "tsConfig": "apps/web/tsconfig.app.json",
19 "assets": ["apps/web/src/assets"],
20 "styles": ["apps/web/src/styles.css"]
21 },
22 "configurations": {
23 "development": {
24 "extractLicenses": false,
25 "optimization": false,
26 "sourceMap": true
27 },
28 "production": {
29 "optimization": true,
30 "outputHashing": "all",
31 "sourceMap": false,
32 "extractLicenses": true
33 }
34 }
35 },
36 "serve": {
37 "executor": "@nx/webpack:dev-server",
38 "defaultConfiguration": "development",
39 "options": {
40 "buildTarget": "web:build"
41 },
42 "configurations": {
43 "development": {
44 "buildTarget": "web:build:development"
45 },
46 "production": {
47 "buildTarget": "web:build:production"
48 }
49 }
50 },
51 "test": {
52 "executor": "@nx/jest:jest",
53 "outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
54 "options": {
55 "jestConfig": "apps/web/jest.config.ts",
56 "passWithNoTests": true
57 }
58 },
59 "lint": {
60 "executor": "@nx/eslint:lint",
61 "outputs": ["{options.outputFile}"],
62 "options": {
63 "lintFilePatterns": ["apps/web/**/*.{ts,tsx,js,jsx}"]
64 }
65 }
66 }
67}
Template 3: Module Boundary Rules
json
1// .eslintrc.json
2{
3 "root": true,
4 "ignorePatterns": ["**/*"],
5 "plugins": ["@nx"],
6 "overrides": [
7 {
8 "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
9 "rules": {
10 "@nx/enforce-module-boundaries": [
11 "error",
12 {
13 "enforceBuildableLibDependency": true,
14 "allow": [],
15 "depConstraints": [
16 {
17 "sourceTag": "type:app",
18 "onlyDependOnLibsWithTags": [
19 "type:feature",
20 "type:ui",
21 "type:data-access",
22 "type:util"
23 ]
24 },
25 {
26 "sourceTag": "type:feature",
27 "onlyDependOnLibsWithTags": [
28 "type:ui",
29 "type:data-access",
30 "type:util"
31 ]
32 },
33 {
34 "sourceTag": "type:ui",
35 "onlyDependOnLibsWithTags": ["type:ui", "type:util"]
36 },
37 {
38 "sourceTag": "type:data-access",
39 "onlyDependOnLibsWithTags": ["type:data-access", "type:util"]
40 },
41 {
42 "sourceTag": "type:util",
43 "onlyDependOnLibsWithTags": ["type:util"]
44 },
45 {
46 "sourceTag": "scope:web",
47 "onlyDependOnLibsWithTags": ["scope:web", "scope:shared"]
48 },
49 {
50 "sourceTag": "scope:api",
51 "onlyDependOnLibsWithTags": ["scope:api", "scope:shared"]
52 },
53 {
54 "sourceTag": "scope:shared",
55 "onlyDependOnLibsWithTags": ["scope:shared"]
56 }
57 ]
58 }
59 ]
60 }
61 }
62 ]
63}
Template 4: Custom Generator
typescript
1// tools/generators/feature-lib/index.ts
2import {
3 Tree,
4 formatFiles,
5 generateFiles,
6 joinPathFragments,
7 names,
8 readProjectConfiguration,
9} from '@nx/devkit';
10import { libraryGenerator } from '@nx/react';
11
12interface FeatureLibraryGeneratorSchema {
13 name: string;
14 scope: string;
15 directory?: string;
16}
17
18export default async function featureLibraryGenerator(
19 tree: Tree,
20 options: FeatureLibraryGeneratorSchema
21) {
22 const { name, scope, directory } = options;
23 const projectDirectory = directory
24 ? `${directory}/${name}`
25 : `libs/${scope}/feature-${name}`;
26
27 // Generate base library
28 await libraryGenerator(tree, {
29 name: `feature-${name}`,
30 directory: projectDirectory,
31 tags: `type:feature,scope:${scope}`,
32 style: 'css',
33 skipTsConfig: false,
34 skipFormat: true,
35 unitTestRunner: 'jest',
36 linter: 'eslint',
37 });
38
39 // Add custom files
40 const projectConfig = readProjectConfiguration(tree, `${scope}-feature-${name}`);
41 const projectNames = names(name);
42
43 generateFiles(
44 tree,
45 joinPathFragments(__dirname, 'files'),
46 projectConfig.sourceRoot,
47 {
48 ...projectNames,
49 scope,
50 tmpl: '',
51 }
52 );
53
54 await formatFiles(tree);
55}
Template 5: CI Configuration with Affected
yaml
1# .github/workflows/ci.yml
2name: CI
3
4on:
5 push:
6 branches: [main]
7 pull_request:
8 branches: [main]
9
10env:
11 NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}
12
13jobs:
14 main:
15 runs-on: ubuntu-latest
16 steps:
17 - uses: actions/checkout@v4
18 with:
19 fetch-depth: 0
20
21 - uses: actions/setup-node@v4
22 with:
23 node-version: 20
24 cache: 'npm'
25
26 - name: Install dependencies
27 run: npm ci
28
29 - name: Derive SHAs for affected commands
30 uses: nrwl/nx-set-shas@v4
31
32 - name: Run affected lint
33 run: npx nx affected -t lint --parallel=3
34
35 - name: Run affected test
36 run: npx nx affected -t test --parallel=3 --configuration=ci
37
38 - name: Run affected build
39 run: npx nx affected -t build --parallel=3
40
41 - name: Run affected e2e
42 run: npx nx affected -t e2e --parallel=1
Template 6: Remote Caching Setup
typescript
1// nx.json with Nx Cloud
2{
3 "tasksRunnerOptions": {
4 "default": {
5 "runner": "nx-cloud",
6 "options": {
7 "cacheableOperations": ["build", "lint", "test", "e2e"],
8 "accessToken": "your-nx-cloud-token",
9 "parallel": 3,
10 "cacheDirectory": ".nx/cache"
11 }
12 }
13 },
14 "nxCloudAccessToken": "your-nx-cloud-token"
15}
16
17// Self-hosted cache with S3
18{
19 "tasksRunnerOptions": {
20 "default": {
21 "runner": "@nx-aws-cache/nx-aws-cache",
22 "options": {
23 "cacheableOperations": ["build", "lint", "test"],
24 "awsRegion": "us-east-1",
25 "awsBucket": "my-nx-cache-bucket",
26 "awsProfile": "default"
27 }
28 }
29 }
30}
Common Commands
bash
1# Generate new library
2nx g @nx/react:lib feature-auth --directory=libs/web --tags=type:feature,scope:web
3
4# Run affected tests
5nx affected -t test --base=main
6
7# View dependency graph
8nx graph
9
10# Run specific project
11nx build web --configuration=production
12
13# Reset cache
14nx reset
15
16# Run migrations
17nx migrate latest
18nx 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