Le monorepo est devenu l'architecture de reference pour les equipes qui gerent plusieurs applications TypeScript partageant du code commun. Vercel, Google, Microsoft, Uber — les plus grandes equipes d'ingenierie au monde utilisent des monorepos. Mais configurer un monorepo correctement, avec les bons outils et les bonnes pratiques, reste un defi technique que beaucoup d'equipes sous-estiment. Ce guide vous accompagne pas a pas pour creer un monorepo TypeScript professionnel avec Turborepo (orchestration des taches et cache) et pnpm (gestion des dependances et workspaces), deux outils qui forment ensemble la stack monorepo la plus performante et la plus simple a mettre en place en 2026.
Etape 1 — Initialiser le workspace pnpm et la structure du monorepo
La premiere etape consiste a creer la structure de base du monorepo avec pnpm. pnpm est le gestionnaire de paquets ideal pour les monorepos grace a son systeme de liens symboliques qui evite la duplication des dependances et son support natif des workspaces. Contrairement a npm ou yarn, pnpm stocke les paquets dans un store global et cree des liens symboliques dans chaque workspace, ce qui reduit drastiquement l'espace disque et accelere les installations.
# Creer le dossier racine et initialiser
mkdir mon-monorepo && cd mon-monorepo
pnpm init
# Creer la structure de dossiers
mkdir -p apps/web apps/api
mkdir -p packages/ui packages/config packages/types
mkdir -p tooling/scripts
# Creer le fichier pnpm-workspace.yaml
cat > pnpm-workspace.yaml << 'EOF'
packages:
- "apps/*"
- "packages/*"
- "tooling/*"
EOF
# Creer le .npmrc pour la configuration pnpm
cat > .npmrc << 'EOF'
auto-install-peers=true
strict-peer-dependencies=false
EOFLe fichier pnpm-workspace.yaml definit les trois zones du monorepo : apps/ pour les applications deployables, packages/ pour les librairies partagees, et tooling/ pour les scripts et utilitaires de build. Cette separation tripartite est une convention largement adoptee par l'ecosysteme Turborepo et facilite la comprehension de la codebase par les nouveaux arrivants. Chaque sous-dossier devient un workspace independant avec son propre package.json.
Initialisez chaque workspace avec un package.json minimal. Le point crucial est le champ name : utilisez un scope npm (par exemple @monrepo/web, @monrepo/ui) pour eviter les conflits de noms et faciliter les imports internes.
// apps/web/package.json
{
"name": "@monrepo/web",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "next dev --port 3000",
"build": "next build",
"start": "next start",
"lint": "eslint . --max-warnings 0"
},
"dependencies": {
"@monrepo/ui": "workspace:*",
"@monrepo/types": "workspace:*",
"next": "^15.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}Notez l'utilisation de workspace:* pour les dependances internes. Ce protocole specifique a pnpm indique que la dependance doit etre resolue depuis le workspace local plutot que depuis le registre npm. C'est ce qui permet a apps/web d'importer directement les composants de packages/ui sans publication prealable sur npm.
Etape 2 — Configurer Turborepo et definir les pipelines de build
Installez Turborepo comme dependance de developpement a la racine du monorepo et creez le fichier de configuration turbo.json. C'est ce fichier qui definit les pipelines — les relations entre les taches (build, test, lint) et leurs dependances.
# Installer Turborepo
pnpm add -D turbo -w
# turbo.json a la racine
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"globalEnv": ["NODE_ENV", "CI"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "tsconfig.json", "package.json"],
"outputs": [".next/**", "dist/**", "build/**"],
"env": ["NEXT_PUBLIC_*"]
},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "test/**", "vitest.config.*"],
"outputs": ["coverage/**"]
},
"lint": {
"dependsOn": ["^build"],
"inputs": ["src/**", ".eslintrc.*", "eslint.config.*"]
},
"typecheck": {
"dependsOn": ["^build"],
"inputs": ["src/**", "tsconfig.json"]
}
}
}Les points cles de cette configuration sont les suivants. Le ^build dans dependsOn signifie "build les dependances en amont d'abord" — si apps/web depend de packages/ui, Turborepo s'assure que ui est build avant web. Les inputs definissent les fichiers surveilles pour le cache — si aucun input n'a change, Turborepo sert le resultat du cache. Les outputs indiquent quels dossiers doivent etre mis en cache. La tache dev a cache: false car le mode dev ne doit pas etre cache, et persistent: true car les serveurs de dev restent actifs.
Ajoutez les scripts a la racine du package.json pour lancer les pipelines avec turbo :
// package.json (racine)
{
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"test": "turbo test",
"lint": "turbo lint",
"typecheck": "turbo typecheck",
"clean": "turbo clean && rm -rf node_modules"
}
}Etape 3 — Creer les packages partages (UI, config, types)
Les packages partages sont la raison d'etre d'un monorepo. Ils permettent de mutualiser le code entre applications sans passer par la publication npm. Nous allons creer trois packages fondamentaux : ui (composants React reutilisables), config (configurations ESLint, TypeScript, Tailwind), et types (interfaces et types TypeScript partages).
// packages/ui/package.json
{
"name": "@monrepo/ui",
"version": "0.0.0",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint . --max-warnings 0",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@monrepo/config": "workspace:*",
"tsup": "^8.0.0",
"typescript": "^5.6.0"
},
"peerDependencies": {
"react": "^19.0.0"
}
}
// packages/ui/src/index.ts
export { Button } from "./button";
export { Card } from "./card";
// packages/ui/src/button.tsx
import React from "react";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "primary" | "secondary" | "outline";
size?: "sm" | "md" | "lg";
}
export function Button({
variant = "primary",
size = "md",
className = "",
children,
...props
}: ButtonProps) {
const base = "rounded-lg font-medium transition-colors";
const variants = {
primary: "bg-blue-600 text-white hover:bg-blue-700",
secondary: "bg-gray-700 text-white hover:bg-gray-600",
outline: "border border-gray-600 text-gray-300 hover:bg-gray-800",
};
const sizes = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg",
};
return (
<button
className={`${base} ${variants[variant]} ${sizes[size]} ${className}`}
{...props}
>
{children}
</button>
);
}// packages/types/package.json
{
"name": "@monrepo/types",
"version": "0.0.0",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts"
}
// packages/types/src/index.ts
export interface User {
id: string;
email: string;
name: string;
role: "admin" | "user" | "viewer";
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
status: "success" | "error";
message?: string;
pagination?: {
page: number;
limit: number;
total: number;
};
}L'utilisation de tsup pour le build du package ui est un choix delibere : tsup est un bundler zero-config base sur esbuild qui genere les formats CJS et ESM avec les declarations de types. Pour les packages purement TypeScript comme types, aucun build n'est necessaire si vous utilisez les path aliases TypeScript (etape 4).
Etape 4 — Configurer TypeScript avec les path aliases et project references
La configuration TypeScript dans un monorepo repose sur un tsconfig de base a la racine et des tsconfig specifiques dans chaque workspace qui etendent la config de base. Les project references TypeScript permettent de build les packages dans le bon ordre et d'optimiser le type-checking incremental.
// packages/config/tsconfig/base.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"incremental": true
},
"exclude": ["node_modules", "dist", ".turbo"]
}
// apps/web/tsconfig.json
{
"extends": "@monrepo/config/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@monrepo/ui": ["../../packages/ui/src"],
"@monrepo/types": ["../../packages/types/src"]
},
"plugins": [{ "name": "next" }]
},
"include": ["src", "next-env.d.ts", ".next/types/**/*.ts"],
"references": [
{ "path": "../../packages/ui" },
{ "path": "../../packages/types" }
]
}Les paths permettent a TypeScript et votre IDE de resoudre les imports vers les sources des packages locaux directement, ce qui active l'autocompletion, le "Go to Definition" et le renommage cross-package. Les references indiquent a tsc --build l'ordre de compilation. Avec Next.js 15+, le transpilation des packages du monorepo est automatique grace a la configuration transpilePackages dans next.config.js.
Etape 5 — Mettre en place le cache local et distant (Vercel Remote Cache)
Le cache est la fonctionnalite qui rend Turborepo si performant. Par defaut, Turborepo maintient un cache local dans le dossier .turbo. Quand vous lancez turbo build, Turborepo calcule un hash base sur les inputs de chaque tache (fichiers source, dependances, variables d'environnement). Si le hash correspond a un build precedent, le resultat est servi directement depuis le cache — zero recompilation.
Le cache distant etend ce mecanisme a toute l'equipe. Au lieu de stocker les resultats uniquement localement, ils sont uploades vers un serveur partage. Quand un collegue lance le meme build, il recupere le resultat depuis le cache distant au lieu de le recalculer. Pour activer le cache distant avec Vercel :
# Authentification avec Vercel
npx turbo login
# Lier le monorepo a votre equipe Vercel
npx turbo link
# Verifier que le cache distant fonctionne
turbo build --summarize
# Vous verrez "Remote Cache: Enabled" dans le summary
# Pour un serveur de cache custom (sans Vercel) :
# Ajoutez dans turbo.json :
{
"remoteCache": {
"signature": true,
"enabled": true
}
}
# Et definissez les variables d'environnement :
# TURBO_API=https://votre-cache-server.com
# TURBO_TOKEN=votre-token
# TURBO_TEAM=votre-equipePour les equipes qui ne veulent pas dependre de Vercel, des serveurs de cache open source sont disponibles : turborepo-remote-cache (Node.js, stockage S3/GCS/Azure) et turbo-cache (Rust, haute performance). Le gain typique avec le cache distant est de 40 a 80% de reduction du temps de CI, car les packages inchanges ne sont jamais rebuilds. Pour les projets TypeScript qui utilisent deja des pipelines GitHub Actions, notre guide sur la configuration de pipelines CI/CD avec GitHub Actions fournit les bases necessaires.
Besoin d'aide pour configurer votre monorepo ?
Architecture monorepo, migration depuis multi-repo, optimisation CI/CD, cache distant — notre equipe d'architectes vous accompagne.
Obtenir mon devis gratuitEtape 6 — Integrer le CI/CD avec GitHub Actions et les builds incrementaux
L'integration de Turborepo avec GitHub Actions tire parti du cache distant et du filtrage des taches pour ne builder que ce qui a change. La configuration ci-dessous est un workflow production-ready qui optimise les temps de CI tout en garantissant la qualite du code a chaque pull request.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
jobs:
build:
name: Build & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Pour turbo --filter
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm typecheck
- name: Test
run: pnpm test
# Build uniquement les apps modifiees pour les PRs
build-affected:
name: Build Affected
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- name: Build affected packages
run: pnpm turbo build --filter=...[HEAD~1]Le flag --filter=...[HEAD~1] est la cle : il indique a Turborepo de ne builder que les packages qui ont change depuis le dernier commit. Si seul packages/ui a change, seuls ui et les apps qui en dependent seront rebuilds. Les packages inchanges recuperent leur resultat du cache distant. Pour les equipes qui gerent la securite de leurs pipelines CI/CD, nous recommandons egalement notre guide sur la securisation de GitHub Actions contre les attaques supply chain.
Etape 7 — Deployer les applications avec isolation des builds
Le deploiement d'applications depuis un monorepo necessite d'isoler chaque app avec uniquement ses dependances, sans embarquer l'integralite du monorepo. Turborepo fournit la commande turbo prune qui cree un sous-ensemble minimal du monorepo contenant uniquement une app et ses dependances internes.
# Dockerfile pour apps/web (multi-stage)
FROM node:22-alpine AS base
RUN corepack enable && corepack prepare pnpm@9 --activate
# Stage 1: Prune the monorepo
FROM base AS pruner
WORKDIR /app
COPY . .
RUN turbo prune @monrepo/web --docker
# Stage 2: Install dependencies
FROM base AS installer
WORKDIR /app
COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
RUN pnpm install --frozen-lockfile
# Stage 3: Build
COPY --from=pruner /app/out/full/ .
RUN pnpm turbo build --filter=@monrepo/web
# Stage 4: Run
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=installer /app/apps/web/.next/standalone ./
COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer /app/apps/web/public ./apps/web/public
EXPOSE 3000
CMD ["node", "apps/web/server.js"]Le turbo prune --docker genere trois dossiers optimises pour un Dockerfile multi-stage : out/json (uniquement les package.json pour l'installation des dependances), out/full (le code source necessaire), et les fichiers lockfile. Cette separation maximise l'utilisation du cache Docker : si seul le code change mais pas les dependances, le layer d'installation est servi depuis le cache Docker.
Pour un deploiement sur Vercel, la configuration est encore plus simple. Vercel detecte automatiquement Turborepo et optimise les builds. Il suffit de configurer le "Root Directory" sur apps/web dans les parametres du projet Vercel, et de definir le "Build Command" sur cd ../.. && turbo build --filter=@monrepo/web. Vercel gere le cache distant automatiquement si votre monorepo est lie. Pour approfondir le deploiement Next.js, notre guide sur deployer Next.js en production en 5 etapes couvre les aspects complementaires.
FAQ
Pourquoi choisir Turborepo plutot que Nx pour un monorepo TypeScript ?
Turborepo se concentre sur l'orchestration des taches avec un systeme de cache incremental extremement performant et une configuration minimale. Nx est plus complet mais aussi plus complexe, avec des generateurs de code, des plugins par framework, et une courbe d'apprentissage plus importante. Pour un monorepo TypeScript avec pnpm, Turborepo offre le meilleur rapport simplicite/performance. Choisissez Nx si vous avez besoin de scaffolding automatise, de plugins specifiques (Angular, React Native), ou d'un graph de dependances interactif integre. Pour la plupart des equipes, Turborepo est le choix le plus pragmatique en 2026.
Comment fonctionne le cache distant de Turborepo avec Vercel ?
Le cache distant stocke les resultats de build sur les serveurs Vercel (ou un serveur custom). Quand un developpeur ou le CI execute une tache, Turborepo calcule un hash base sur les inputs (fichiers source, dependances, variables d'environnement). Si ce hash existe dans le cache distant, le resultat est telecharge au lieu d'etre recalcule. L'activation se fait en deux commandes : npx turbo login puis npx turbo link. Le gain typique est de 40-80% sur les temps de CI.
Peut-on utiliser Turborepo sans Vercel pour le cache distant ?
Oui. Turborepo supporte les serveurs de cache custom via l'API Remote Cache. Des solutions open source comme turborepo-remote-cache (Node.js) et turbo-cache (Rust) permettent de stocker les artefacts sur S3, GCS, Azure Blob Storage ou un serveur local. La configuration se fait via les variables d'environnement TURBO_API, TURBO_TOKEN et TURBO_TEAM. C'est l'option recommandee pour les entreprises soumises a des contraintes de souverainete des donnees.
Comment gerer les versions des packages dans un monorepo Turborepo ?
L'outil recommande est Changesets. Installez-le avec pnpm add -D @changesets/cli -w et initialisez avec pnpm changeset init. Le workflow est : 1) pnpm changeset pour declarer un changement, 2) pnpm changeset version pour bumper les versions et generer les changelogs, 3) pnpm changeset publish pour publier sur npm. Pour les packages internes non publies, utilisez workspace:* dans les dependances pour toujours pointer vers la version locale.
Architecture monorepo, migration et optimisation CI/CD
Migration multi-repo vers monorepo, configuration Turborepo avancee, cache distant, pipelines GitHub Actions — notre equipe d'architectes vous accompagne.
Demander un accompagnementArticles lies :
- Configurer un pipeline CI/CD avec GitHub Actions en 7 etapes
- Deployer Next.js en production en 5 etapes
- Securiser GitHub Actions contre les attaques supply chain en 6 etapes
- Developpeur TypeScript fullstack France : 27 entretiens en 7 etapes
Sources : Documentation Turborepo, pnpm Workspaces
