nx-workspace-patterns

Nx monorepo configuration patterns for workspace setup, project boundaries, and build optimization. Provides architectural templates for organizing apps, libraries, and tools with five library types (feature, ui, data-access, util, shell) and scope-based dependency rules Includes ready-to-use configurations for nx.json, project.json, module boundary enforcement via ESLint, and custom generators to maintain consistency Covers build caching setup with cacheable operations, named inputs for granular cache invalidation, and remote caching via Nx Cloud or self-hosted S3 backends Demonstrates CI integration with affected commands to run only tests and builds for changed projects, plus custom generator patterns for scaffolding feature libraries

INSTALLATION
npx skills add https://github.com/wshobson/agents --skill nx-workspace-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card