Back to blog
March 8, 2024
6 min read

Nx Monorepos: Why Your Slow CI/CD Isn't a Reason to Split

Nx fundamentals and why long pipeline runs shouldn't drive you to microservices

Nx Monorepos: Why Your Slow CI/CD Isn’t a Reason to Split

“Our builds take 45 minutes, we need to break this into microservices!”

Stop right there. This is like burning down your house because the dishwasher is slow.

Let explain why build time alone should never drive architectural decisions by walking thorugh applying Nx.

What is Nx?

Nx is a build system with first-class monorepo support. But calling it just a “build tool” is like calling a Swiss Army knife “just a blade.”

Core Concepts

1. Workspace
Your entire codebase lives in one repository with multiple projects (apps and libraries).

2. Project Graph
Nx understands dependencies between your projects and builds an intelligent graph.

3. Affected Commands
Only build, test, and lint what actually changed. Not everything.

4. Computation Caching
Results are cached locally and remotely. Never rebuild the same thing twice.

5. Task Orchestration
Run tasks in the optimal order based on dependencies.

Basic Nx Setup

# Create a new Nx workspace
npx create-nx-workspace@latest myworkspace

# Generate applications
nx g @nx/react:app frontend
nx g @nx/node:app backend

# Generate libraries
nx g @nx/react:lib shared-ui
nx g @nx/node:lib shared-utils

Your workspace structure:

myworkspace/
├── apps/
│   ├── frontend/
│   └── backend/
├── libs/
│   ├── shared-ui/
│   └── shared-utils/
└── nx.json

The Magic: Affected Detection

Here’s where Nx shines. Instead of running tests on everything:

# Traditional approach (slow)
npm test

# Nx approach (fast)
nx affected:test

Nx analyzes your git history and only tests projects affected by your changes.

Example:
You change shared-ui library. Nx automatically knows to test:

  • shared-ui itself
  • frontend app (depends on shared-ui)
  • NOT backend app (doesn’t use shared-ui)

Result: 5-minute test run instead of 45 minutes.

Real-World Performance Gains

Before Nx

# Every CI run
- Install dependencies: 3 min
- Lint everything: 8 min
- Test everything: 25 min
- Build everything: 12 min
Total: 48 minutes

After Nx

# Typical CI run (small change)
- Install dependencies: 3 min (cached)
- Lint affected: 1 min
- Test affected: 3 min
- Build affected: 2 min
Total: 9 minutes (cached dependencies)

83% reduction in build time without changing architecture.

Companies Doing Monoliths Right

Google

  • Largest monorepo in the world: 2 billion lines of code
  • 9 million source files in a single repository
  • Build time: Minutes, not hours (thanks to Bazel, Nx’s inspiration)

Facebook/Meta

  • React, Jest, Yarn: All developed in monorepos
  • Millions of lines of JavaScript/TypeScript
  • Developer productivity: Optimized for rapid iteration

Microsoft

  • VS Code: 1.5+ million lines in monorepo
  • Office: Large parts developed as monolith
  • Build optimization: Heavy investment in tooling

Shopify

  • Core platform: Rails monolith with 3+ million lines
  • 2000+ developers working on the same codebase
  • Deploy frequency: Multiple times per day

Netflix

  • Edge services: Many are substantial monoliths
  • Rapid deployment: Despite large codebases
  • Lesson: They split for organizational reasons, not build times

Advanced Nx Features

1. Remote Caching

// nx.json
{
  "tasksRunnerOptions": {
    "default": {
      "runner": "@nrwl/nx-cloud",
      "options": {
        "cacheableOperations": ["build", "test", "lint"],
        "accessToken": "your-token"
      }
    }
  }
}

Share build artifacts across your entire team. If someone already built it, you get instant results.

2. Distributed Task Execution

# Run tests across multiple CI agents
nx affected:test --parallel=3

Nx intelligently distributes work across available resources.

3. Project Boundaries

// .eslintrc.json
{
  "rules": {
    "@nx/enforce-module-boundaries": [
      "error",
      {
        "depConstraints": [
          {
            "sourceTag": "scope:admin",
            "onlyDependOnLibsWithTags": ["scope:admin", "scope:shared"]
          }
        ]
      }
    ]
  }
}

Enforce architectural boundaries without splitting repositories.

When NOT to Split Your Monolith

Bad Reasons

  • “Our builds are slow” → Use Nx (or other tools), not microservices
  • “The repository is large” → Git handles massive repos fine
  • “We want faster deploys” → Optimize your deployment pipeline
  • “It’s hard to navigate” → Improve your IDE setup and tooling

Good Reasons

  • Team ownership boundaries are naturally separate
  • Different scaling requirements for system components
  • Compliance requirements demand isolation
  • Technology stack divergence is beneficial

Pipeline Optimization Strategies

1. Smart Dependency Management

// Project configuration
{
  "name": "frontend",
  "dependencies": ["shared-ui", "shared-utils"],
  "implicitDependencies": ["!backend"] // Explicitly independent
}

2. Incremental Builds

# Only rebuild what changed
nx build frontend --with-deps --skip-nx-cache=false

3. Parallel Execution

# Run multiple tasks simultaneously
nx run-many --target=test --projects=frontend,shared-ui --parallel=2

4. Smart Test Selection

// Only run tests for changed code
{
  "testMatch": ["<rootDir>/src/**/*.spec.ts"],
  "testPathIgnorePatterns": ["node_modules"],
  "collectCoverageFrom": ["src/**/*.ts"]
}

The Real Benefits of Monorepos

1. Atomic Changes
Change an API and all its consumers in one commit. No coordination overhead.

2. Simplified Dependency Management
One package.json, one lock file, consistent versions everywhere.

3. Easy Refactoring
Rename a function across 50 files with confidence. Your IDE sees everything.

4. Shared Tooling
One linting config, one testing setup, one build configuration.

5. Cross-Project Insights
See how your changes affect the entire system immediately.

Getting Started with Nx

1. Migration Strategy

# Add Nx to existing project
npx nx@latest init

Start by adding Nx to your current setup. No need for a full rewrite.

2. Gradual Adoption

  • Week 1: Add affected commands to CI
  • Week 2: Set up caching
  • Week 3: Extract shared libraries
  • Week 4: Add project boundaries

3. Measure Improvements

Track your improvements:

  • Build time reduction
  • Test execution time
  • Developer feedback
  • Deploy frequency

The Bottom Line

Your slow CI/CD is a tooling problem, not an architecture problem.

Before you split your codebase:

  1. Try Nx or similar tools (Bazel, Rush, Lerna)
  2. Optimize your pipelines with caching and parallelization
  3. Measure the impact of each optimization
  4. Consider splitting only after tooling exhaustion

Companies like Google, Facebook, and Shopify prove that massive codebases can have fast, efficient development workflows. The secret isn’t splitting everything—it’s investing in the right tools.

Nx gives you the best of both worlds: monolith simplicity with microservice-like build performance. Your future self will thank you for choosing tools over architectural complexity.