Retour au blog

Monorepos avec Nx et Turborepo : Comment Scaler les Projets JavaScript en 2025

Salut HaWkers, les monorepos sont devenus le standard de facto pour les projets JavaScript à grande échelle en 2025. Google, Facebook, Microsoft, Uber et pratiquement toutes les big techs gèrent des millions de lignes de code dans des dépôts uniques.

Pourquoi ? Parce que les monorepos avec des outils modernes comme Nx et Turborepo résolvent des problèmes qui étaient impossibles à résoudre avec les multi-repos : partage de code sans friction, builds incrémentaux qui économisent des heures, et refactoring atomique cross-projects.

Vous gérez encore 15 dépôts séparés qui partagent 80% du code ? Il est temps d'évoluer. Explorons comment les monorepos modernes ont changé la donne.

Le Problème Que les Monorepos Résolvent

Imaginez : vous avez un e-commerce avec frontend web, app mobile, admin panel, APIs et microservices. Dans le modèle traditionnel (multi-repo) :

Scénario Multi-Repo :

  • 8 dépôts différents
  • Chacun avec son package.json, CI/CD, versioning
  • Code dupliqué dans tous (utils, types, constants)
  • Mettre à jour une dépendance ? 8 PRs séparées
  • Refactorer une interface partagée ? Coordination cauchemardesque
  • Builds indépendants sans optimisation

Scénario Monorepo (2025) :

  • 1 dépôt
  • Code partagé sans duplication
  • Mettre à jour dépendance ? 1 commit
  • Refactoring atomique de toute la codebase
  • Builds intelligents qui recompilent uniquement ce qui a changé
  • Cache distribué partagé entre développeurs
// Structure typique de Monorepo en 2025
monorepo-ecommerce/
├── apps/
│   ├── web/                  // App Next.js
│   ├── mobile/               // React Native
│   ├── admin/                // Admin panel
│   └── api/                  // API Express
├── packages/
│   ├── ui/                   // Composants partagés
│   ├── utils/                // Utilitaires partagés
│   ├── types/                // Types TypeScript partagés
│   ├── config/               // Configs partagées
│   └── data-access/          // Clients API partagés
├── tools/
│   ├── scripts/              // Scripts build
│   └── generators/           // Générateurs de code
├── nx.json                   // Configuration Nx
├── turbo.json                // Configuration Turborepo
└── package.json              // package.json racine

// Avant (multi-repo) : Code dupliqué dans 8 endroits
// packages/web/src/utils/formatCurrency.ts
// packages/mobile/src/utils/formatCurrency.ts
// packages/admin/src/utils/formatCurrency.ts
// ... (5 copies de plus)

// Après (monorepo) : Une seule source de vérité
// packages/utils/src/formatCurrency.ts
// Utilisé par TOUS les apps sans duplication

Nx vs Turborepo : Lequel Choisir ?

Les deux dominent le marché de 2025, mais ont des philosophies différentes :

Nx : Le Framework Complet

# Créer workspace Nx
npx create-nx-workspace@latest my-monorepo

# Nx est opinionné et fournit tout out-of-the-box

Avantages de Nx :

  • Generators : Scaffolding automatisé d'apps/libs
  • Dependency graph : Visualisation claire des dépendances
  • Affected commands : Exécute uniquement tests du code affecté
  • Plugin ecosystem : Intégrations prêtes (React, Next, Nest, etc)
  • Smart rebuilds : Cache intelligent local et distant
// nx.json - Configuration Nx
{
  "tasksRunnerOptions": {
    "default": {
      "runner": "nx-cloud",
      "options": {
        "cacheableOperations": ["build", "test", "lint"],
        "accessToken": "your-token",
        "parallel": 3
      }
    }
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["production", "^production"]
    },
    "test": {
      "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
    }
  }
}

// Commandes puissantes
// Exécute uniquement tests affectés par changements
nx affected:test

// Build uniquement apps affectés
nx affected:build

// Visualise dependency graph
nx graph

// Génère nouveau composant
nx generate @nx/react:component Button --project=ui

Turborepo : Minimaliste et Rapide

# Créer workspace Turborepo
npx create-turbo@latest

Avantages de Turborepo :

  • Simplicité : Configuration minimale
  • Performance extrême : Builds parallèles agressifs
  • Remote caching : Intégration native Vercel
  • Zero config : Fonctionne avec n'importe quelle structure
  • Flexibilité : Sans opinions fortes
// turbo.json - Configuration Turborepo
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"],
      "cache": true
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "outputs": [],
      "cache": true
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  },
  "globalDependencies": [
    "tsconfig.json",
    ".eslintrc.js"
  ],
  "remoteCache": {
    "signature": true
  }
}
# Commandes Turborepo
# Build tous les packages
turbo build

# Build uniquement ce qui a changé (incrémental)
turbo build --filter=...HEAD

# Build avec remote cache
turbo build --api="https://api.vercel.com" --token="your-token"

# Exécuter en parallèle avec limite
turbo build --concurrency=10

La Puissance du Build Incrémental

Le différentiel des monorepos modernes : ne jamais rebuild ce qui n'a pas changé.

// Exemple pratique : E-commerce avec 10 packages

// Scénario 1 : SANS build incrémental
// Développeur change 1 ligne dans packages/utils
// Build traditionnel :
// ❌ Rebuilds : web (5min) + mobile (8min) + admin (4min) + api (3min)
// ❌ Total : 20 minutes

// Scénario 2 : AVEC Nx/Turborepo
// Même changement dans packages/utils
// Build intelligent :
// ✅ Rebuilds uniquement : utils (10s) + apps qui en dépendent
// ✅ Utilise cache pour le reste
// ✅ Total : 2 minutes

// Économie : 18 minutes = 90% plus rapide !

Implémentation Pratique : Remote Caching

// packages/build-tools/cache-utils.ts
import { createHash } from 'crypto';
import { readFileSync } from 'fs';

export class BuildCache {
  private cacheEndpoint: string;

  constructor(endpoint: string) {
    this.cacheEndpoint = endpoint;
  }

  async getCachedBuild(inputs: string[]): Promise<Buffer | null> {
    // Génère hash des inputs
    const hash = this.hashInputs(inputs);

    try {
      // Cherche dans cache distant
      const response = await fetch(`${this.cacheEndpoint}/cache/${hash}`);

      if (response.ok) {
        console.log(`✅ Cache HIT pour ${hash}`);
        return Buffer.from(await response.arrayBuffer());
      }

      console.log(`❌ Cache MISS pour ${hash}`);
      return null;
    } catch (error) {
      console.warn('Récupération cache échouée :', error);
      return null;
    }
  }

  async storeBuild(inputs: string[], output: Buffer): Promise<void> {
    const hash = this.hashInputs(inputs);

    try {
      await fetch(`${this.cacheEndpoint}/cache/${hash}`, {
        method: 'PUT',
        body: output,
        headers: {
          'Content-Type': 'application/octet-stream'
        }
      });

      console.log(`✅ Build stocké dans cache : ${hash}`);
    } catch (error) {
      console.warn('Stockage cache échoué :', error);
    }
  }

  private hashInputs(inputs: string[]): string {
    const hash = createHash('sha256');

    for (const input of inputs) {
      const content = readFileSync(input);
      hash.update(content);
    }

    return hash.digest('hex');
  }

  async executeWithCache<T>(
    inputs: string[],
    buildFn: () => Promise<T>
  ): Promise<T> {
    // Essaie de récupérer du cache
    const cached = await this.getCachedBuild(inputs);

    if (cached) {
      return JSON.parse(cached.toString()) as T;
    }

    // Si non trouvé, exécute build
    const result = await buildFn();

    // Stocke dans cache
    await this.storeBuild(inputs, Buffer.from(JSON.stringify(result)));

    return result;
  }
}

// Usage dans script build
const cache = new BuildCache('https://cache.mycompany.com');

async function buildPackage(packageName: string) {
  const inputs = [
    `packages/${packageName}/src/**/*.ts`,
    `packages/${packageName}/package.json`,
    `packages/${packageName}/tsconfig.json`
  ];

  return cache.executeWithCache(inputs, async () => {
    console.log(`🔨 Building ${packageName}...`);

    // Build réel ici
    const result = await runBuild(packageName);

    return result;
  });
}

Graphe de Dépendances : Comprendre les Impacts

// packages/dependency-analyzer/graph.ts
import { ProjectGraph } from '@nx/devkit';

class DependencyAnalyzer {
  constructor(private graph: ProjectGraph) {}

  findAffectedProjects(changedFiles: string[]): Set<string> {
    const affected = new Set<string>();

    // Pour chaque fichier changé
    for (const file of changedFiles) {
      const project = this.findProjectByFile(file);

      if (project) {
        // Ajoute projet directement affecté
        affected.add(project);

        // Ajoute tous les projets qui en dépendent
        const dependents = this.findDependents(project);
        dependents.forEach(dep => affected.add(dep));
      }
    }

    return affected;
  }

  findDependents(projectName: string): Set<string> {
    const dependents = new Set<string>();

    // Cherche tous les projets qui dépendent de projectName
    for (const [name, project] of Object.entries(this.graph.nodes)) {
      const deps = project.data.dependencies || [];

      if (deps.some(dep => dep.target === projectName)) {
        dependents.add(name);

        // Récursif : trouve les dépendants des dépendants
        const transitive = this.findDependents(name);
        transitive.forEach(dep => dependents.add(dep));
      }
    }

    return dependents;
  }

  generateBuildPlan(changedFiles: string[]): {
    projects: string[];
    estimatedTime: number;
    canParallelize: string[][];
  } {
    const affected = this.findAffectedProjects(changedFiles);

    // Détermine lesquels peuvent s'exécuter en parallèle
    const parallelGroups = this.groupParallelBuilds(Array.from(affected));

    return {
      projects: Array.from(affected),
      estimatedTime: this.estimateBuildTime(affected),
      canParallelize: parallelGroups
    };
  }
}

// Usage en CI/CD
const analyzer = new DependencyAnalyzer(projectGraph);

const changedFiles = getChangedFilesSinceLastCommit();
const buildPlan = analyzer.generateBuildPlan(changedFiles);

console.log('📊 Plan de Build :');
console.log(`  Projets affectés : ${buildPlan.projects.length}`);
console.log(`  Temps estimé : ${Math.ceil(buildPlan.estimatedTime / 60)} minutes`);
console.log(`  Groupes parallèles : ${buildPlan.canParallelize.length}`);

// Exécute builds en parallèle
for (const group of buildPlan.canParallelize) {
  await Promise.all(
    group.map(project => buildPackage(project))
  );
}

Quand Utiliser les Monorepos ?

Les monorepos ne conviennent pas à tous les cas. Utilisez quand :

Multiples projets liés : Frontend + Backend + Mobile partageant du code
Partage de code fréquent : Composants, utils, types
Refactoring cross-project : Changements affectant multiples projets
CI/CD complexe : Besoin d'orchestrer builds de multiples projets

Évitez quand :

  • Projet unique simple
  • Équipes complètement indépendantes
  • Projets sans code partagé
  • Infrastructure CI limitée

Conclusion : L'Avenir de la Gestion du Code

Les monorepos avec Nx ou Turborepo sont devenus un outil essentiel pour les projets modernes. La capacité de scaler sans friction, combinée avec des builds intelligents et du cache distribué, et du refactoring atomique en font un choix évident pour 2025.

Si vous êtes intéressé par d'autres tendances d'architecture moderne, voyez TypeScript en 2025 : Dominant le Marché, où nous explorons comment Type Safety s'intègre parfaitement avec les monorepos.

C'est parti ! 🦅

📚 Vous Voulez Approfondir Vos Connaissances en JavaScript ?

Cet article a couvert les monorepos et l'architecture avancée, mais il y a bien plus à explorer dans le monde du développement moderne.

Les développeurs qui investissent dans des connaissances solides et structurées tendent à avoir plus d'opportunités sur le marché.

Matériel d'Étude Complet

Si vous voulez maîtriser JavaScript du basique à l'avancé, j'ai préparé un guide complet :

Options d'investissement :

  • €9,90 (paiement unique)

👉 Découvrir le Guide JavaScript

💡 Matériel mis à jour avec les meilleures pratiques du marché

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires