optimisation core web vitals 7 min

Git et Core Web Vitals : les 15 points qui plombent vos métriques

Commits, hooks, historique, gitignore : 15 pièges Git qui dégradent LCP, INP et TTFB sans que les devs s'en aperçoivent. Audit terrain.

Par Julien Morel
Partager

On te dira que Git n’a rien à voir avec les Core Web Vitals. Que c’est un outil de devs, une tuyauterie qu’on branche une fois, et qu’ensuite le LCP, l’INP ou le TTFB se règlent côté serveur et côté front. C’est faux. Pas parce que Git mesure le rendu. Parce que Git gouverne ce qui entre dans le build, ce qui sort du cache, et à quelle vitesse le code arrive sur ton serveur.

Cas classique : un stagiaire pousse une archive assets.zip dans public/, personne ne relit le diff du commit. Le fichier n’est même pas référencé dans le JSX. Mais il atterrit dans le bundle statique, servi par le CDN, préchargé par le <link rel="preload"> automatique du framework. Le Largest Contentful Paint dérive sur mobile et l’équipe ne sait pas pourquoi. Git n’a pas causé la lenteur. Git l’a rendue possible.

Le fichier qui pourrit le build ne s’appelle pas forcément index.js

Il s’appelle node_modules.tar.gz, bck_2024.zip, fonts_backup.woff2, ou video_homepage_test.mp4. Vous les tolérez parce que « c’est temporaire » ou « au cas où ». Ils traversent le git add . qu’on exécute un vendredi soir. S’ils atterrissent dans public/ ou dans un dossier importé par Webpack, le bundler les embarque. Même un fichier ignoré par le routeur mais présent dans l’arborescence statique peut déclencher des préchargements, des fetchpriority="high" par défaut sur certains frameworks, ou pire, se retrouver dans le graphe de dépendances CSS via un url().

Cas typique : un projet versionne toutes ses polices dans app/assets/fonts/, déclinaisons et formats compris, EOT pour des navigateurs morts depuis dix ans inclus. Le .gitignore ne filtre que node_modules. Le reste passe par git add ., un commit -m "fonts update", et le déploiement automatique recopie tout. Le LCP de la landing stagne parce que le navigateur télécharge une graisse italique avant le texte principal. Quand quelqu’un finit par croiser le jour du commit avec la dégradation dans la Search Console, plusieurs mois se sont écoulés.

⚠️ Attention : ajouter un fichier .gitignore à postériori n’efface pas les binaires de l’historique. Les clones suivants récupèrent toujours la totale. La commande git filter-branch ou git filter-repo devient nécessaire pour nettoyer le dépôt, avec les risques de réécriture que ça comporte.

Les hooks pre-commit ne sont pas un bouclier magique pour le LCP

Husky, lint-staged, prettier, eslint, vous les utilisez probablement. Le piège, c’est la chaîne qu’ils déclenchent sans que personne en ait une vision globale. Un classique : le hook pre-commit qui exécute un npm run build complet à chaque commit, soi-disant pour vérifier que le code compile. Le commit met des dizaines de secondes à passer. Les devs contournent avec --no-verify. Deux sprints plus tard, plus personne ne passe le build en local, et le CI de staging montre des régressions de LCP que personne ne détecte avant la recette.

Un autre classique : le hook qui lance un audit Lighthouse sur chaque modification de composant. Si le seuil n’est pas atteint, le commit est rejeté. Mais l’audit tourne sur un serveur local, avec un CPU partagé, et les scores varient d’une machine à l’autre. Les devs ajustent les seuils à la baisse pour pouvoir merger. Le hook devient un bruit de fond, et le vrai LCP remonte dans Crashlytics sans qu’on réagisse.

Les hooks sont utiles quand ils ciblent une unique responsabilité : formater le code avec Prettier, vérifier qu’aucun console.log ne part en prod, interdire les fichiers de plus de 200 Ko dans public/. Dès qu’ils veulent remplacer une CI ou un monitoring RUM, ils produisent l’effet inverse. L’équipe développe une cécité aux alertes, et le LCP se dégrade silencieusement commit après commit.

Une mauvaise stratégie de cache invalidation peut être commitée par erreur

Le TTFB est la métrique la plus sensible aux régressions Git parce qu’il suffit d’un changement anodin pour invalider tout le cache CDN. Je pense à ce fichier _headers ou vercel.json modifié dans un commit qui active une règle Cache-Control: no-store sur l’ensemble des pages, parce que le dev voulait tester un revalidate en local. Le commit passe en review. Le relecteur voit une ligne de config, pense que c’est lié à une nouvelle fonctionnalité, approuve. La mise en production vide le cache de milliers de visiteurs pendant que le serveur reconstruit chaque page. TTFB multiplié par 4. Le temps que quelqu’un fouille git log -- _headers, trois heures se sont écoulées.

Autre scenario : vous migrez vers l’App Router de Next.js 14 et vous changez le nom des fichiers d’assets. Le système de build leur attribue un nouveau hash. Git voit un renommage et un contenu identique, le développeur commit sans se douter que le CDN va considérer ces fichiers comme des entrées neuves et purger les anciennes du cache. Résultat : tous les utilisateurs actifs retéléchargent le bundle à la prochaine navigation. Le FCP bondit de 400 ms. L’outillage de monitoring le détectera, mais le mal est fait.

Il n’y a pas de solution technique unique, seulement une discipline d’équipe : chaque commit qui touche à la configuration de cache doit être revu avec la même rigueur qu’un commit qui touche à la boucle de rendu. Un fichier Cache-Control mal configuré n’est pas un détail ops, c’est une régression Core Web Vitals.

L’historique Git lui-même peut devenir un frein au TTFB

Les dépôts Git lourds pénalisent le clonage. Si la CI clone l’intégralité du dépôt à chaque déploiement, un .git/ obèse rallonge mécaniquement le délai avant que le serveur ne commence à builder. La cause habituelle : des binaires versionnés au lieu d’être externalisés (maquettes Photoshop, exports vidéos, copies de node_modules). Shallow clone (git clone --depth 1) sur la CI et assets lourds sur un bucket S3 ramènent le clone à quelques secondes.

Les dépendances et le state management ne se gèrent pas dans Git comme on le croit

Beaucoup de projets React versionnent leur package-lock.json avec discipline, mais oublient que Git conserve chaque version antérieure. Après deux ans, l’historique contient des centaines de versions du lockfile, soit plusieurs mégaoctets de diffs illisibles. La taille du fichier ralentit les merges et complexifie la revue. Pire : des dépendances transientes obsolètes persistent dans l’historique et peuvent être réactivées par erreur lors d’un cherry-pick malheureux.

Quand vous ajoutez une librairie de state management comme Zustand, le package.json change et les bundles se recomposent. Si un commit antérieur réutilise un hook dans un composant critique sans qu’on ait repassé un audit de performance, le rendu peut basculer côté client et faire grimper l’INP. Schéma typique sur une fiche produit : un composant « ajouter au panier » importe un selector Zustand qui, après une montée de version mineure, déclenche un re-render en cascade de la page entière. L’INP triple. La cause est dans un commit de mise à jour de dépendance, pas dans du code métier. Le diff ne montre qu’un changement de version, jamais le changement de comportement interne de la lib.

Git n’est pas fait pour auditer les side effects de dépendances. Pourtant, c’est le seul journal dont on dispose. Annoter chaque mise à jour de dépendance avec une snapshot Lighthouse dans le message de commit change la donne : un git log --grep="Lighthouse" retrouve le dernier état stable.

Questions fréquentes

Est-ce que Git LFS résout le problème des fichiers binaires pour les Core Web Vitals ?

Git LFS déporte les binaires sur un serveur dédié et les remplace par des pointeurs. Le clone reste rapide, l’historique s’allège. Mais LFS ne dispense pas d’une politique de non-versionnement pour les assets qui ne doivent pas être servis par le front. Si un fichier .psd n’est jamais utilisé dans le build, il n’a pas à être dans le dépôt, avec ou sans LFS. Le premier impact sur les Web Vitals vient du fait que le fichier existe dans le périmètre du projet, pas du mécanisme de stockage.

Comment convaincre une équipe que la taille du dépôt est un enjeu de performance ?

On ne convainc pas avec un argument abstrait. On fait un clone du dépôt sur une CI standard, on mesure le temps de git clone + npm ci, et on le compare à la même opération après avoir retiré 100 Mo de binaires de l’historique. Si le gain est de 20 secondes par déploiement et que l’équipe déploie 10 fois par jour, ce sont des heures de disponibilité et de TTFB stable gagnées chaque semaine. Le chiffre parle plus qu’un discours. Et si ça coince, un petit extrait du log git-sizer calme souvent les sceptiques.

Faut-il versionner le dossier .next ou dist dans Git ?

Jamais. Ces dossiers sont le résultat du build, pas une source. Les versionner crée une duplication monstrueuse à chaque commit, rend les diffs illisibles et risque d’injecter en production des assets avec des hashs qui écrasent le cache CDN. La CI rebuild toujours à partir de la source, c’est la seule façon de garantir un déploiement déterministe et des métriques comparables.

Articles similaires

Julien Morel

Julien Morel

Ancien dev front React passé SEO technique après une migration e-commerce qui a fait perdre 60% du trafic organique à son employeur en une nuit (fichier robots.txt oublié en staging). Depuis, il écrit pour que ça n'arrive à personne d'autre et teste sur ses propres side-projects avant de publier quoi que ce soit.

Cet article est publie a titre informatif. Faites vos propres recherches avant toute decision.