Skip to main content

Scaling Teams with NX Monorepo Architecture

Lessons learned from migrating multiple teams to a unified NX monorepo with shared tooling.

December 20, 2024
12 min read
NXMonorepoArchitecture

Why Monorepo?

When I joined Tradeshift and later helped build Semfi, the codebase was split across multiple repositories. This led to:

  • Dependency hell: Different versions of shared libraries
  • Inconsistent tooling: Each repo had its own build system
  • Difficult refactoring: Cross-repo changes were painful
  • Code duplication: Shared logic copied between repos
  • The NX Solution

    NX provides a structured approach to monorepo management:

  • Dependency graph: Understand relationships between projects
  • Affected commands: Only rebuild what changed
  • Code generation: Consistent project scaffolding
  • Plugin system: Extend functionality as needed
  • Migration Strategy

    We migrated incrementally:

  • Phase 1: Set up NX workspace with shared tooling
  • Phase 2: Move frontend apps into the monorepo
  • Phase 3: Migrate backend services (NestJS)
  • Phase 4: Extract shared libraries
  • Phase 5: Implement code generation
  • Key Learnings

    1. Start with Tooling

    Before migrating code, ensure your tooling is solid:

  • ESLint configuration that works for all projects
  • Consistent TypeScript settings
  • Shared CI/CD pipelines
  • 2. Embrace Generators

    NX generators ensure consistency. We created custom generators for:

  • New React components
  • API endpoints
  • Feature modules
  • 3. Invest in Boundaries

    Use NX's module boundary rules to enforce architecture:

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

    Results

    After the full migration:

  • 50% faster CI using affected commands
  • Shared component library (40+ components)
  • Type-safe API integration across all apps
  • Easier onboarding with consistent patterns