Monorepos avec Nx et Turborepo : Comment les Grandes Entreprises Gèrent leurs Projets en 2025
Salut HaWkers, si vous avez déjà travaillé dans une entreprise avec plusieurs projets liés - une app mobile, un dashboard admin, un site public, une API - vous avez probablement ressenti la douleur de maintenir du code dupliqué, des versions désalignées de bibliothèques partagées, et des déploiements qui ressemblent à un orchestre sans chef.
Vous êtes-vous déjà demandé comment des entreprises comme Google, Facebook et Microsoft arrivent à gérer des centaines de projets qui partagent du code sans sombrer dans le chaos ? Et plus important : comment pouvez-vous appliquer ces mêmes stratégies à vos projets, même dans des équipes plus petites ?
Qu'est-ce qu'un Monorepo et Pourquoi C'est Important en 2025
Un monorepo est une stratégie d'organisation de code où plusieurs projets liés vivent dans le même dépôt Git. Au lieu d'avoir un dépôt pour chaque app, vous avez un seul dépôt contenant tous vos projets, partageant code, outils et pipelines.
En 2025, les monorepos sont passés d'une curiosité des géants de la tech à un standard mainstream. Les outils ont mûri, la documentation s'est améliorée, et les bénéfices sont devenus impossibles à ignorer :
Avantages clairs :
- Partage de code simplifié : Partagez des bibliothèques entre projets sans publier sur npm
- Refactoring atomique : Changez une API et mettez à jour tous les consommateurs dans le même commit
- Cohérence : Mêmes versions de dépendances, mêmes outils, mêmes standards
- Visibilité : Voyez comment les changements affectent tous les projets
- CI/CD optimisé : Exécutez tests et builds uniquement sur ce qui a changé
Défis résolus en 2025 :
- Performance des outils (Nx et Turborepo sont 7x+ plus rapides)
- Complexité du setup (templates prêts et documentation claire)
- Scalabilité (support pour des milliers de projets)
Nx : La Solution Enterprise-Grade
Nx est un outil de monorepo puissant, créé par Nrwl, focalisé sur la scalabilité et l'expérience développeur. En 2025, Nx s'est consolidé comme le choix pour les monorepos complexes et polyglots (plusieurs langages).
Caractéristiques de Nx
- Task orchestration intelligente : Nx comprend les dépendances entre projets et exécute les tâches dans le bon ordre
- Computation caching : Les résultats de builds et tests sont mis en cache localement et à distance
- Affected commands : Exécutez des tâches uniquement sur les projets affectés par des changements
- Plugins pour tout : React, Angular, Node, Go, Rust, et plus
- Dependency graph : Visualisation interactive de comment les projets sont liés
Setup d'un Monorepo Nx
# Créer un nouveau workspace Nx
npx create-nx-workspace@latest my-workspace
# Choisissez les options :
# - Package-based monorepo ou Integrated monorepo
# - TypeScript/JavaScript
# - CI/CD provider (GitHub Actions, GitLab, etc)
cd my-workspace
# Ajouter des applications
nx g @nx/react:app web
nx g @nx/react:app admin
nx g @nx/node:app api
# Ajouter des bibliothèques partagées
nx g @nx/js:lib shared-ui
nx g @nx/js:lib shared-utils
nx g @nx/js:lib data-accessStructure d'un Monorepo Nx
my-workspace/
├── apps/
│ ├── web/ # App React du site public
│ ├── admin/ # Dashboard administratif
│ └── api/ # Backend Node.js
├── libs/
│ ├── shared-ui/ # Composants UI partagés
│ ├── shared-utils/ # Utilitaires partagés
│ └── data-access/ # Client API partagé
├── tools/
│ └── generators/ # Générateurs personnalisés
├── nx.json # Configuration Nx
├── package.json
└── tsconfig.base.jsonConfiguration nx.json
{
"extends": "nx/presets/npm.json",
"tasksRunnerOptions": {
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"parallel": 3
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"outputs": ["{projectRoot}/dist"]
},
"test": {
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": ["!{projectRoot}/**/*.spec.ts"]
}
}
Travailler avec des Bibliothèques Partagées
// libs/shared-ui/src/lib/Button.tsx
import React from 'react';
export interface ButtonProps {
variant?: 'primary' | 'secondary';
onClick?: () => void;
children: React.ReactNode;
}
export function Button({ variant = 'primary', onClick, children }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{children}
</button>
);
}
// libs/shared-ui/src/index.ts - Barrel export
export * from './lib/Button';
export * from './lib/Input';
export * from './lib/Modal';Utilisation dans les apps :
// apps/web/src/app/app.tsx
import { Button } from '@my-workspace/shared-ui';
export function App() {
return (
<div>
<h1>Bienvenue sur l'App Web</h1>
<Button variant="primary" onClick={() => console.log('Cliqué')}>
Cliquez-moi
</Button>
</div>
);
}Data Access Layer Partagé
// libs/data-access/src/lib/api-client.ts
export class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`);
if (!response.ok) {
throw new Error(`Erreur API: ${response.statusText}`);
}
return response.json();
}
async post<T, D>(endpoint: string, data: D): Promise<T> {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Erreur API: ${response.statusText}`);
}
return response.json();
}
}
// libs/data-access/src/lib/users-service.ts
import { ApiClient } from './api-client';
export interface User {
id: string;
name: string;
email: string;
}
export class UsersService {
constructor(private api: ApiClient) {}
async getUsers(): Promise<User[]> {
return this.api.get<User[]>('/users');
}
async getUser(id: string): Promise<User> {
return this.api.get<User>(`/users/${id}`);
}
async createUser(user: Omit<User, 'id'>): Promise<User> {
return this.api.post<User, Omit<User, 'id'>>('/users', user);
}
}
// libs/data-access/src/index.ts
export * from './lib/api-client';
export * from './lib/users-service';Utilisation dans les apps :
// apps/web/src/services/index.ts
import { ApiClient, UsersService } from '@my-workspace/data-access';
const apiClient = new ApiClient(process.env.NX_API_URL || 'http://localhost:3333');
export const usersService = new UsersService(apiClient);
// apps/web/src/app/users-page.tsx
import { useEffect, useState } from 'react';
import { usersService } from '../services';
import { User } from '@my-workspace/data-access';
export function UsersPage() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
usersService.getUsers()
.then(setUsers)
.finally(() => setLoading(false));
}, []);
if (loading) return <div>Chargement...</div>;
return (
<div>
<h1>Utilisateurs</h1>
{users.map(user => (
<div key={user.id}>
{user.name} - {user.email}
</div>
))}
</div>
);
}
Turborepo : Vitesse et Simplicité
Turborepo, créé par Vercel, mise sur la simplicité et la performance extrême. Il est plus léger que Nx mais incroyablement efficace pour les monorepos JavaScript/TypeScript.
Caractéristiques de Turborepo
- Zero config cache : Cache distribué avec zéro configuration
- Incremental builds : Ne rebuild que ce qui a changé
- Remote caching : Partagez le cache avec toute l'équipe
- Lean : Philosophie minimaliste, exploite les outils existants
- Intégration Vercel : Intégration native avec Vercel
Setup d'un Monorepo Turborepo
# Créer un nouveau monorepo
npx create-turbo@latest
cd my-turborepo
# Structure générée :
# apps/
# web/ # Next.js app
# docs/ # Next.js docs site
# packages/
# ui/ # Composants UI partagés
# eslint-config/
# tsconfig/
# turbo.json
# package.jsonConfiguration turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"cache": true
},
"dev": {
"cache": false,
"persistent": true
}
}
}Workspace Package.json
{
"name": "my-turborepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint"
},
"devDependencies": {
"turbo": "latest"
}
}Package UI Partagé
// packages/ui/src/Button.tsx
import * as React from 'react';
export interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary';
}
export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
return (
<button
onClick={onClick}
className={`button button--${variant}`}
>
{children}
</button>
);
}
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.0",
"main": "./src/index.tsx",
"types": "./src/index.tsx",
"license": "MIT",
"scripts": {
"lint": "eslint src/",
"test": "jest"
},
"devDependencies": {
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@types/react": "^18.2.0",
"react": "^18.2.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
}
// packages/ui/src/index.tsx
export * from './Button';
export * from './Input';
export * from './Card';
Remote Caching avec Vercel
# Connecter au Vercel Remote Cache
npx turbo login
# Lier au projet
npx turbo link
# Maintenant tous les builds sont mis en cache dans le cloud
# Toute l'équipe bénéficie du cache partagé// turbo.json avec remote cache
{
"$schema": "https://turbo.build/schema.json",
"remoteCache": {
"signature": true
},
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
}
}
}Nx vs Turborepo : Quand Utiliser Chacun
Utilisez Nx quand :
- Projets polyglots : Besoin de support pour plusieurs langages (TypeScript, Go, Rust, Python)
- Échelle enterprise : Centaines de projets, multiples équipes
- Personnalisation profonde : Besoin de générateurs personnalisés, plugins spécifiques
- Dependency graph complexe : Besoin de visualisation et analyse profonde des dépendances
- Conformance rules : Besoin de garantir des standards dans toute l'organisation
Utilisez Turborepo quand :
- JavaScript/TypeScript uniquement : Focus total sur l'écosystème JS
- Simplicité : Configuration minimale, exploite les outils existants
- Écosystème Vercel : Utilise Next.js et déploiements sur Vercel
- Performance pure : Priorité à la vitesse absolue de build
- Équipe petite/moyenne : Pas besoin de toute la complexité de Nx
// Comparaison de performance (projet moyen avec 20 apps)
const benchmarks = {
nx: {
coldBuild: '45s',
cachedBuild: '2.1s',
affectedBuild: '8.3s',
features: ['affected', 'graph', 'plugins', 'generators']
},
turborepo: {
coldBuild: '38s',
cachedBuild: '1.8s',
affectedBuild: '7.1s',
features: ['cache', 'pipeline', 'remote-cache']
}
};
console.table(benchmarks);
// Les deux sont extrêmement rapides, choisissez selon les features nécessaires
Stratégies d'Organisation du Code
Boundary Rules (Nx)
// .eslintrc.json - Enforce les limites entre modules
{
"overrides": [
{
"files": ["*.ts"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"allow": [],
"depConstraints": [
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:feature", "type:ui", "type:util"]
},
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": ["type:ui", "type:util", "type:data-access"]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:util"]
}
]
}
]
}
}
]
}Système de Tagging
// libs/shared-ui/project.json
{
"name": "shared-ui",
"tags": ["type:ui", "scope:shared"]
}
// libs/web-feature-auth/project.json
{
"name": "web-feature-auth",
"tags": ["type:feature", "scope:web"]
}
// libs/data-access/project.json
{
"name": "data-access",
"tags": ["type:data-access", "scope:shared"]
}Code Generators (Nx)
// tools/generators/component/index.ts
import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';
export default async function (tree: Tree, schema: any) {
const projectRoot = `libs/${schema.project}/src/lib`;
tree.write(
`${projectRoot}/${schema.name}.tsx`,
`import React from 'react';
export interface ${schema.name}Props {
children?: React.ReactNode;
}
export function ${schema.name}({ children }: ${schema.name}Props) {
return (
<div>
<h1>${schema.name}</h1>
{children}
</div>
);
}
`
);
tree.write(
`${projectRoot}/${schema.name}.spec.tsx`,
`import { render } from '@testing-library/react';
import { ${schema.name} } from './${schema.name}';
describe('${schema.name}', () => {
it('devrait rendre correctement', () => {
const { baseElement } = render(<${schema.name} />);
expect(baseElement).toBeTruthy();
});
});
`
);
await formatFiles(tree);
return () => {
installPackagesTask(tree);
};
}
// Utilisation:
// nx g @my-workspace/tools:component MyComponent --project=shared-ui
CI/CD Optimisé pour Monorepos
GitHub Actions avec Nx
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- run: npm ci
- uses: nrwl/nx-set-shas@v3
- run: npx nx affected -t lint test build --parallel=3
env:
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}GitHub Actions avec Turborepo
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npx turbo run build test lint
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}Meilleures Pratiques Monorepo 2025
- Utilisez le workspace protocol pour les dépendances internes
{
"dependencies": {
"@my-workspace/shared-ui": "workspace:*"
}
}- Versionnez les bibliothèques partagées sémantiquement
nx release version --skip-publish
nx release publish- Configurez les paths dans tsconfig.base.json
{
"compilerOptions": {
"paths": {
"@my-workspace/shared-ui": ["libs/shared-ui/src/index.ts"],
"@my-workspace/*": ["libs/*/src/index.ts"]
}
}
}- Utilisez conventional commits pour changelogs automatiques
git commit -m "feat(shared-ui): ajoute nouvelle variante de Button"
git commit -m "fix(api): résout bug d'authentification"- Implémentez les code owners
# CODEOWNERS
/libs/shared-ui/ @frontend-team
/apps/api/ @backend-team
/libs/data-access/ @full-stack-teamLe Futur des Monorepos
En 2025, les monorepos se sont consolidés comme la façon préférée d'organiser le code dans les entreprises de toutes tailles. Les outils sont matures, la communauté est forte, et les bénéfices sont clairs.
Ce qui arrive :
- Génération de code IA-powered : Générateurs qui comprennent votre pattern et suggèrent du code
- Encore plus de performance : Outils écrits en Rust/Go devenant encore plus rapides
- Cloud development environments : Workspaces tournant dans le cloud avec cache partagé
- Monorepos cross-language : Support encore meilleur pour projets polyglots
- Mises à jour de dépendances automatisées : IA qui met à jour les dépendances de façon sécurisée
Les monorepos ne sont pas une solution miracle, mais pour la majorité des organisations avec plusieurs projets liés, c'est le meilleur choix en 2025. La question n'est plus "si", mais "quand" faire la transition.
Si vous voulez continuer à approfondir les outils et pratiques modernes de développement, je recommande de revenir au début de cette série : Marché du Développement Logiciel en 2025 : Tendances, Salaires et Compétences en Forte Demande où vous aurez une vision complète du marché actuel.
C'est parti !
📚 Vous Voulez Approfondir Vos Connaissances en JavaScript ?
Cet article a couvert les monorepos et l'architecture de projets, mais il y a beaucoup 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 niveau débutant au niveau 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é

