Skip to main content

Nx Workspace and Monorepo Development

Nx is a build system specialized for monorepo management of large-scale applications including Angular. Adopted by companies like Google, Microsoft, and Cisco, it enables team boundary management, build acceleration, and code consistency.

1. What is Nx?

Overview and Philosophy

Nx is a smart, fast build system. It efficiently manages multiple projects within a monorepo and solves the following challenges:

  • Reduced build times: Caching and impact analysis ensure only tasks related to changes are executed
  • Easier code sharing: Simple library creation and sharing prevents code duplication
  • Dependency management: Automatically detects inter-project dependencies and optimizes build order
  • Consistency: Generators and boundary rules unify coding conventions across teams

Relationship with Angular CLI

Nx operates as an extension of Angular CLI. You use nx commands instead of ng commands, but internally it leverages Angular CLI functionality.

# Angular CLI
ng serve my-app
ng build my-app

# Nx (equivalent commands)
nx serve my-app
nx build my-app

Integrated Monorepo vs Package-Based Monorepo

ApproachDescriptionBest For
IntegratedNx centrally manages all projects. Unified build, test, and lint executionNew monorepos, Angular projects
Package-BasedEach package has its own package.json. Similar to npm/yarn workspacesGradual migration of existing projects

Comparison with Turborepo

FeatureNxTurborepo
CachingLocal + Remote (Nx Cloud)Local + Remote (Vercel)
Code GenerationGenerators (powerful)None
Project GraphInteractive visual graphBasic graph
Module BoundariesEnforceable via ESLint rulesNone
Primary TargetAngular, React, Node.js, etc.Primarily Next.js / React ecosystem

2. Nx Workspace Setup

Creating a Workspace

npx create-nx-workspace@latest my-workspace

You'll be prompted to select:

  1. Workspace type: Angular (Integrated Monorepo)
  2. Application name: Name for the first application
  3. Style format: CSS / SCSS / Less, etc.
  4. Nx Cloud: Enable remote caching (recommended)

Workspace Structure

my-workspace/
├── apps/ # Applications
│ └── my-app/
│ ├── src/
│ ├── project.json # Project-specific configuration
│ └── tsconfig.json
├── libs/ # Shared libraries
│ └── shared/
│ └── ui/
├── nx.json # Global Nx config (caching, task settings)
├── tsconfig.base.json # Base TypeScript config
└── package.json

Adding Nx to Existing Angular Projects

# Add Nx to an existing Angular CLI project
npx nx init

Running nx init preserves the existing project structure while adding Nx's caching and task execution capabilities.

3. Creating Projects and Libraries

Creating an Application

nx generate @nx/angular:application my-new-app

Creating a Library

nx generate @nx/angular:library my-lib

Library Types and Naming Conventions

Nx best practices categorize libraries into the following types:

TypeDirectoryPurposeExample
featurelibs/feature/Smart components, pagesfeature-dashboard
uilibs/ui/Presentational componentsui-button, ui-card
data-accesslibs/data-access/API communication, state managementdata-access-user
utillibs/util/Utility functions, helpersutil-date, util-format
modellibs/model/Interfaces, type definitionsmodel-user, model-order
# Example: Create libraries for user features
nx generate @nx/angular:library feature-user-profile --directory=libs/feature
nx generate @nx/angular:library ui-avatar --directory=libs/ui
nx generate @nx/angular:library data-access-user --directory=libs/data-access

buildable vs publishable Libraries

OptionDescriptionUse Case
buildableCan be built independently. Better cache utilizationUsed only within the monorepo
publishableCan be published as an npm packageLibraries also used by external projects
# Create a publishable library
nx generate @nx/angular:library shared-ui --publishable --importPath=@my-org/shared-ui

4. Project Graph (Dependency Visualization)

The nx graph Command

# Display interactive graph in browser
nx graph

The project graph automatically detects dependencies from import statements and TypeScript path aliases.

Use Cases

  • Architecture overview: Visually grasp the overall project structure
  • Circular dependency detection: Identify circular references on the graph
  • Impact analysis: Show which projects are affected by changes to a specific library
# Show graph focused on a specific project
nx graph --focus=my-app

# Show graph of affected projects
nx affected:graph

5. affected Command (Impact Analysis)

The affected command analyzes Git diffs and runs tasks only on projects impacted by changes.

# Build only affected projects
nx affected -t build

# Test only affected projects
nx affected -t test

# Lint only affected projects
nx affected -t lint

Usage in CI/CD

.github/workflows/ci.yml
- name: Run affected tests
run: npx nx affected -t test --base=origin/main --head=HEAD

By specifying comparison commits with --base and --head, only the minimum necessary builds and tests are run per PR. This dramatically reduces CI time even in large monorepos.

6. Caching and Remote Caching

Local Cache

Nx locally caches the results of tasks with identical inputs (source code + configuration). Subsequent runs skip execution and return results from cache.

# First run: Normal execution (seconds to minutes)
nx build my-app

# Second run: Cache hit (milliseconds)
nx build my-app
# > nx run my-app:build [local cache]

Nx Cloud Remote Cache

To share cache across the entire team, you can use Nx Cloud's remote caching.

# Connect to Nx Cloud
npx nx connect-to-nx-cloud

Remote cache benefits:

  • Team member A's build results can be reused by member B
  • CI execution results can be reused in local development

Distributed Task Execution (DTE)

Nx Cloud's distributed task execution automatically distributes tasks across multiple machines in CI pipelines.

.github/workflows/ci.yml
- name: Start CI run
run: npx nx-cloud start-ci-run --distribute-on="5 linux-medium-js"

- name: Run tasks
run: npx nx affected -t lint test build

Cache Target Configuration

nx.json
{
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"]
},
"test": {
"cache": true
},
"lint": {
"cache": true
}
}
}

7. Module Boundary Rules

@nx/enforce-module-boundaries

Using ESLint rules, you can automatically detect and prohibit invalid dependencies between projects. This is critical for maintaining architecture in large teams.

Project Classification with Tags

Set tags in each project's project.json:

libs/feature/user-profile/project.json
{
"tags": ["type:feature", "scope:user"]
}
libs/ui/button/project.json
{
"tags": ["type:ui", "scope:shared"]
}

Dependency Rules with depConstraints

.eslintrc.json
{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"depConstraints": [
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": ["type:ui", "type:data-access", "type:util", "type:model"]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:ui", "type:util", "type:model"]
},
{
"sourceTag": "type:data-access",
"onlyDependOnLibsWithTags": ["type:data-access", "type:util", "type:model"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util", "type:model"]
},
{
"sourceTag": "type:model",
"onlyDependOnLibsWithTags": ["type:model"]
}
]
}
]
}
}

This configuration enforces the following rules:

  • feature → Can depend on ui, data-access, util, model (cannot depend on other features)
  • ui → Can depend on ui, util, model (cannot depend on data-access or feature)
  • data-access → Can depend on data-access, util, model
  • model → Can only depend on model
Comparison with React/Next.js

The React/Next.js ecosystem has no standard mechanism for enforcing module boundary rules. Large projects typically address this with restrictive eslint-plugin-import rules or custom ESLint rules.

8. Generators

Nx Generators vs Angular Schematics

FeatureNx GeneratorsAngular Schematics
PositionPart of Nx ecosystemPart of Angular CLI
TargetMulti-framework: Angular, React, Node.js, etc.Angular-specific
TestingEasy unit testing with JestSomewhat complex
Dry RunPreview with --dry-runSame

Creating Custom Generators

You can define team-specific code generation templates as generators.

# Create a generator plugin library
nx generate @nx/plugin:plugin my-plugin

# Add a custom generator
nx generate @nx/plugin:generator my-generator --project=my-plugin

Custom generators are used to unify team boilerplate and auto-generate new components and services with a consistent structure.

9. Micro Frontends with Nx

Nx supports building micro frontends using Module Federation.

Creating Host and Remotes

# Create the host application (shell)
nx generate @nx/angular:host shell --remotes=app1,app2

# Add a remote application
nx generate @nx/angular:remote app3 --host=shell

Module Federation Configuration

apps/shell/module-federation.config.ts
import { ModuleFederationConfig } from '@nx/webpack';

const config: ModuleFederationConfig = {
name: 'shell',
remotes: ['app1', 'app2'],
};

export default config;

Shared Library Management

  • Nx automatically deduplicates shared libraries in Module Federation configuration
  • Libraries in libs/ are shared as singletons, reducing bundle size
  • Each remote app can be built and deployed independently

10. CI/CD Pipeline Integration

GitHub Actions Configuration Example

.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:

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'

- run: npm ci

- uses: nrwl/nx-set-shas@v4

- run: npx nx affected -t lint test build

Key Points

  • fetch-depth: 0 fetches complete Git history (needed for affected diff calculation)
  • nrwl/nx-set-shas automatically sets the base commit
  • nx affected targets only projects affected by changes

11. Nx Console (VS Code Extension)

Nx Console is available as a VS Code extension, providing a GUI for Nx operations.

  • Generator execution: Set options in a form interface to generate components, libraries, etc.
  • Project graph: Display interactive dependency graphs within VS Code
  • Task execution: Run build / serve / test / lint with one click
  • affected confirmation: Visually display the impact scope of current changes
tip

For detailed usage, see "VS Code Setup Best Practices".

12. Migrating Existing Projects

Gradual Migration from Angular CLI to Nx

Step 1: Add Nx

npx nx init

At this stage, the existing project structure remains unchanged. Nx's caching and task execution capabilities are added.

Step 2: Split into Libraries

Gradually split existing modules into Nx libraries.

# Create a shared UI library
nx generate @nx/angular:library shared-ui --directory=libs/ui

# Move existing code to the library
# → Update import paths

Step 3: Introduce Boundary Rules

Set tags on libraries and enable @nx/enforce-module-boundaries rules to ensure architectural consistency.

Migration Tips

  • Don't try to migrate everything at once — proceed gradually
  • Just benefiting from caching alone provides significant improvement
  • Start library splitting with the most frequently changed modules
  • Introducing nx affected in CI/CD pipelines early provides great benefits