Angular Monorepo avec Nx : organisation scalable

🏷️ Front-end 📅 14/04/2026 01:45:00 👤 Mezgani said
Angular Nx Monorepo Architecture Ci
Angular Monorepo avec Nx : organisation scalable

Guide pratique pour structurer un monorepo Angular avec Nx : libs partagées, boundaries, caching et workflows CI efficaces.

Pourquoi un monorepo Angular avec Nx ?

Un monorepo regroupe plusieurs applications et bibliothèques dans un seul dépôt Git. Avec Nx, cela apporte une cohérence d'outillage, un partage de code sans publication npm intermédiaire et une gouvernance centralisée des versions de dépendances.

Contrairement à un multirepo, chaque changement cross-app est atomique : une seule PR peut mettre à jour une lib partagée et toutes les apps qui en dépendent simultanément.

Quand choisir un monorepo ? Dès que deux applications partagent du code métier (composants, services, types) ou que l'équipe dépasse 3 développeurs travaillant sur des apps liées. En dessous, un multirepo simple reste souvent plus adapté.

Les bénéfices principaux de Nx pour Angular :

  • Builds et tests incrémentaux : seul l'affecté est recompilé
  • Cache local et distribué (Nx Cloud) pour des CI ultra-rapides
  • Générateurs officiels @nx/angular alignés sur Angular CLI
  • Dependency graph visuel (nx graph)
  • Enforced boundaries pour éviter les couplages involontaires

Créer un workspace Nx Angular

La commande create-nx-workspace initialise le dépôt avec la stack Angular préconfigurée — bundler Vite ou esbuild, ESLint, Jest — selon vos choix interactifs.

# Créer le workspace (mode interactif)
npx create-nx-workspace@latest my-org --preset=angular-monorepo

# Structure générée
my-org/
├── apps/
│   └── my-app/          # Application Angular principale
├── libs/                # Bibliothèques partagées
├── nx.json              # Configuration Nx globale
├── tsconfig.base.json   # Chemins partagés
└── package.json

# Ajouter une deuxième application
nx generate @nx/angular:application admin-app --directory=apps/admin-app

# Ajouter une bibliothèque partagée
nx generate @nx/angular:library ui-button --directory=libs/ui/button

tsconfig.base.json et paths

Nx configure automatiquement les paths TypeScript pour chaque lib générée. Importez @my-org/ui/button directement, sans chemin relatif :

// Dans apps/my-app/src/app/app.component.ts
import { ButtonComponent } from '@my-org/ui/button';

Après génération, lancez nx serve my-app pour démarrer votre app. Nx détecte automatiquement le projet si vous êtes dans son répertoire.

Structure des libs : feature, ui, data-access, util

Nx recommande quatre types de bibliothèques aux responsabilités distinctes. Ce modèle évite les dépendances circulaires et facilite la maintenance à grande échelle.

libs/
├── feature/
│   └── user-profile/        # Smart component + routing
├── ui/
│   └── button/              # Presentational components (pas de services HTTP)
│   └── form-field/
├── data-access/
│   └── user/                # Services HTTP, stores, signals
│   └── auth/
└── util/
    └── date-helpers/        # Pure functions, pipes, validators

Voici un exemple de lib data-access avec un signal store :

// libs/data-access/user/src/lib/user.store.ts
import { signalStore, withState, withMethods } from '@ngrx/signals';
import { inject } from '@angular/core';
import { UserService } from './user.service';

export const UserStore = signalStore(
    { providedIn: 'root' },
    withState({ users: [], loading: false }),
    withMethods((store, service = inject(UserService)) => ({
        async loadUsers() {
            patchState(store, { loading: true });
            const users = await service.getAll();
            patchState(store, { users, loading: false });
        },
    })),
);
Règle d'or : les libs ui ne doivent jamais importer de libs data-access. Les libs feature orchestrent les deux. Cette hiérarchie se formule dans les boundaries ESLint.

Enforced boundaries avec ESLint

La règle @nx/enforce-module-boundaries analyse statiquement les imports à chaque lint et bloque les couplages interdits entre couches ou entre domaines.

Taggez chaque projet dans son project.json :

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

// libs/data-access/user/project.json
{
    "name": "data-access-user",
    "tags": ["scope:user", "type:data-access"]
}

Puis définissez les règles dans eslint.config.js à la racine :

// eslint.config.js (racine)
{
    rules: {
        '@nx/enforce-module-boundaries': ['error', {
            depConstraints: [
                // ui ne peut importer que util
                { sourceTag: 'type:ui',          onlyDependOnLibsWithTags: ['type:util'] },
                // data-access ne peut importer que util
                { sourceTag: 'type:data-access', onlyDependOnLibsWithTags: ['type:util'] },
                // feature peut importer ui, data-access, util
                { sourceTag: 'type:feature',     onlyDependOnLibsWithTags: ['type:ui', 'type:data-access', 'type:util'] },
                // scope isolation : un domaine n'importe pas l'autre
                { sourceTag: 'scope:user',       notDependOnLibsWithTags: ['scope:auth'] },
            ],
        }],
    },
}

Vérifier les violations

Lancez nx lint --all pour auditer l'ensemble du monorepo. En CI, nx affected --target=lint ne teste que les projets modifiés.

Nx affected et cache CI/CD

Nx construit un graphe de dépendances complet. La commande nx affected calcule automatiquement quels projets sont impactés par un diff Git et n'exécute que ceux-là.

# Lancer uniquement les tests affectés par la PR courante
nx affected --target=test --base=origin/main

# Construire uniquement les apps affectées
nx affected --target=build --base=origin/main --parallel=3

# Visualiser le graphe d'impact
nx affected:graph --base=origin/main

Exemple de pipeline GitHub Actions optimisé :

# .github/workflows/ci.yml
name: CI
on: [pull_request]

jobs:
  affected:
    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

      # Cache Nx local
      - uses: actions/cache@v4
        with:
          path: .nx/cache
          key: nx-${{ hashFiles('**/package-lock.json') }}

      - run: npx nx affected --target=lint   --base=origin/main --parallel=3
      - run: npx nx affected --target=test   --base=origin/main --parallel=3
      - run: npx nx affected --target=build  --base=origin/main --parallel=3
Nx Cloud : active le cache distribué entre runners CI. Un build déjà calculé sur une autre machine est récupéré en quelques secondes. Activez-le via nx connect — le plan gratuit couvre la plupart des projets open source.

Checklist monorepo scalable

Avant de merger votre premier projet en monorepo Nx, validez chacun de ces points pour garantir une base saine à long terme.

  • Workspace créé avec create-nx-workspace preset angular-monorepo
  • Chaque lib tagguée scope:xxx + type:xxx dans project.json
  • Règle @nx/enforce-module-boundaries active et couvrant tous les types
  • tsconfig.base.json centralise tous les paths de libs
  • CI utilise nx affected avec --base=origin/main
  • Cache Nx local configuré dans GitHub Actions (action actions/cache)
  • Aucun import relatif cross-lib (utiliser uniquement les alias @my-org/...)
  • Nx Cloud connecté pour partager le cache entre runners parallèles