Un monorepo qui lance la suite de tests complète à chaque commit fonctionne très bien avec dix projets. Avec deux cents, la CI passe vingt minutes à retester du code qu’un commit n’a même pas touché, et l’équipe apprend à ignorer les échecs parce que « c’est probablement un flaky test, pas mon changement ». Le problème n’est pas la taille du monorepo : c’est l’absence de lien explicite entre ce qui a changé et ce qui doit être revérifié.
Le graphe de dépendances remplace le calendrier
Un outil de build monorepo (Nx, Turborepo, Bazel, ou l’équivalent maison) construit d’abord un graphe : chaque package déclare ses dépendances internes, et l’outil en déduit qui dépend de qui. Un commit qui touche packages/auth ne déclenche pas la reconstruction de tout le repo, seulement de auth et de tout ce qui en dépend directement ou transitivement.
# Nx : liste les projets affectés par rapport à la base main
nx show projects --affected --base=main
# Turborepo : équivalent, filtré par changement de working tree
turbo run test --filter=...[main]
La différence avec un simple git diff sur les chemins modifiés est le mot « transitivement » : si payments dépend de auth, un changement dans auth doit revalider payments même si aucun fichier de payments n’a été touché. Un filtrage naïf par chemin de fichier rate cette dépendance et laisse passer une régression.
Le cache distant : ne jamais refaire un travail déjà fait
Le graphe dit quoi reconstruire ; le cache dit si ça vaut la peine de le faire. Chaque tâche (build, test, lint) est hashée à partir de ses entrées réelles : le contenu des fichiers sources, les dépendances déclarées, la version des outils. Si ce hash a déjà été calculé, ailleurs, par quelqu’un d’autre, le résultat est simplement téléchargé depuis un cache distant partagé par toute l’équipe et la CI :
// nx.json (extrait)
{
"tasksRunnerOptions": {
"default": {
"options": {
"cacheableOperations": ["build", "test", "lint"],
"remoteCache": { "url": "https://cache.exemple.internal" }
}
}
}
}
Un développeur qui reconstruit localement une branche déjà buildée par la CI récupère le résultat du cache au lieu de recompiler, ce qui transforme un cache distant efficace en gain de vélocité mesurable bien au-delà de la CI elle-même.
Le piège classique : le hash qui ment
Le cache n’est fiable que si le hash capture réellement tout ce qui influence le résultat. L’erreur la plus fréquente : une variable d’environnement ou un fichier de configuration qui affecte le build mais n’entre pas dans le calcul du hash. Le symptôme est un cache hit qui sert un artefact obsolète, un bug qui « n’apparaît qu’en CI » ou qui disparaît après un --no-cache sans qu’on comprenne pourquoi.
# Diagnostiquer un cache suspect : comparer le hash calculé
# à ce que l'outil aurait dû prendre en compte
nx run payments:build --verbose
# Nx affiche la liste des inputs qui composent le hash de la tâche
La discipline qui évite ce piège : déclarer explicitement chaque input qui influence une tâche (fichiers, variables d’environnement, versions d’outils), et ne jamais supposer que l’outil devine correctement ce qui compte.
Ce que ça change pour l’équipe
Une CI qui ne teste que ce qui est réellement affecté redonne du sens aux échecs : un test rouge signale un vrai problème lié au changement, pas un flaky test noyé dans un run de vingt minutes qui teste tout. C’est aussi ce qui rend un pipeline CI/CD tenable à mesure qu’un monorepo grossit, sans réécrire l’organisation du code pour autant. Cette discipline de build sélectif est le même réflexe qui structure une industrialisation CI/CD réussie : ne revalider que ce qui a réellement changé.
À retenir
Un build monorepo à l’échelle repose sur deux mécanismes distincts : un graphe de dépendances qui détermine ce qui doit être revalidé, et un cache distant qui évite de refaire un travail déjà fait ailleurs. Le graphe doit inclure les dépendances transitives, pas seulement les fichiers modifiés. Le cache n’est fiable que si son hash capture réellement tous les inputs qui influencent le résultat, sans quoi il sert silencieusement des artefacts obsolètes.