En janvier, un client nous contacte. Son LCP a pris 1,2 seconde en une semaine. Le problème : il n’avait aucune donnée d’avant la régression. L’outil SaaS qu’il utilisait ne conservait que 30 jours d’historique sur le plan de base. Impossible de remonter au commit qui a tout cassé. On a passé trois jours à fouiller les logs de déploiement. Depuis, chaque projet commence par un petit script shell de sauvegarde. Un fichier bash de 40 lignes, exécuté par un cron, qui stocke les JSON Lighthouse localement. Voici pourquoi et comment le mettre en place.
L’angle mort des tableaux de bord SaaS
Les dashboards hébergés ont un défaut de conception : ils ne stockent pas vos données, ils les affichent. La nuance est lourde de conséquences. Une montée en gamme tarifaire, une fermeture du service, une mise à jour qui écrase l’historique, et c’est six mois de tendances qui partent en fumée. On l’a vu sur trois projets en deux ans.
Si vous optimisez vos Core Web Vitals sans sauvegarde locale, vous pilotez à vue. Le SaaS vous donne une courbe lissée, souvent agrégée sur 28 jours. Ce dont vous avez besoin pour diagnostiquer une régression, c’est la donnée brute de chaque run, jour après jour, URL par URL. Sans ça, vous ne saurez jamais si le TTFB a dérapé un mardi à 14h après une PR sur le middleware.
Un script shell qui capture les rapports Lighthouse via l’API PageSpeed Insights ne coûte rien, ne dépend d’aucun fournisseur, et produit des fichiers JSON parfaitement lisibles par jq, grep, ou votre prochain pipeline d’analyse. C’est le socle minimal d’une stratégie de performance qui se respecte.
Ce que le script sauvegarde, concrètement
Le script appelle l’API PageSpeed Insights pour une liste d’URLs critiques : page d’accueil, fiche produit la plus lourde, page de catégorie, article le plus visité. Pour chaque URL, il récupère le JSON complet. Ce n’est pas un résumé, c’est l’ensemble des audits Lighthouse : les métriques brutes, les opportunités, les diagnostics.
Le fichier est nommé avec le slug et la date : produit-12_2026-05-11.json. Stocké dans un répertoire metrics/ local. On peut le versionner immédiatement.
Ouvre ton terminal. Crée le dossier metrics. Le cœur de la capture tient en une boucle :
URLS=("https://example.com/" "https://example.com/produit/12")
DATE=$(date +%Y-%m-%d)
for url in "${URLS[@]}"; do
slug=$(echo "$url" | sed 's|https://example.com/||; s|/|-|g')
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&strategy=mobile" > "metrics/${slug}_${DATE}.json"
done
Trois lignes exécutables. Pas de framework, pas de token payant pour l’instant. On peut ajouter un délai entre les requêtes pour ne pas saturer l’API. On peut aussi choisir la stratégie desktop si le trafic mobile n’est pas majoritaire.
Anatomie du script, ligne par ligne
On va décomposer une version complète. Elle fait 45 lignes, elle gère les erreurs et indexe les métriques extraites.
#!/bin/bash
set -euo pipefail
API_KEY="" # Optionnel, pour dépasser le quota gratuit
OUTDIR="./metrics"
mkdir -p "$OUTDIR"
LOG="$OUTDIR/summary_$(date +%Y-%m-%d).csv"
URLS=(
"https://example.com/"
"https://example.com/produit/12"
)
for url in "${URLS[@]}"; do
safe=$(echo "$url" | md5sum | cut -c1-8)
out="${OUTDIR}/${safe}_$(date +%Y-%m-%d).json"
if curl -sf "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&key=${API_KEY}" -o "$out"; then
lcp=$(jq -r '.lighthouseResult.audits["largest-contentful-paint"].numericValue' "$out")
tbt=$(jq -r '.lighthouseResult.audits["total-blocking-time"].numericValue' "$out")
cls=$(jq -r '.lighthouseResult.audits["cumulative-layout-shift"].numericValue' "$out")
echo "$(date +%H:%M:%S),$url,$lcp,$tbt,$cls" >> "$LOG"
else
echo "Échec pour $url à $(date)" >&2
# Optionnel : notifier un canal Slack ou un webhook
fi
sleep 2
done
La variable set -euo pipefail garantit que le script s’arrête si une commande échoue, évitant de stocker un fichier vide. curl -s -f prévient les réponses silencieuses. Le sleep 2 respecte la limite de une requête par seconde de l’API gratuite.
Le fichier CSV de résumé permet un coup d’œil immédiat sans ouvrir les JSON. Une ligne grep sur le LCP, et vous savez si la journée est calme. Les JSON complets restent disponibles pour des analyses plus fines, comme la variation de la distribution des scores.
J’ai écrit la première version de ce script avec Claude Code, et j’avais à l’époque comparé l’expérience avec Cursor IDE dans un autre article. Le shell, c’est un domaine où les LLM proposent souvent des incantations fragiles. L’essentiel est de rester maître de chaque commande.
Automatiser la capture sans devenir dépendant
Un cron quotidien suffit pour détecter des dérives progressives. Une tâche planifiée à 3h du matin évite de fausser les métriques par les pics de trafic. Sur un serveur ou un vieux Raspberry Pi, ça tourne sans maintenance.
Si vous craignez de saturer le quota gratuit, segmentez la capture : une URL différente chaque jour, avec une rotation. Ou déployez un worker Cloudflare qui tourne un équivalent en JavaScript. L’intérêt reste le même : vous possédez les données.
Attention : l’API PageSpeed Insights utilise des conditions réseau simulées. Les résultats peuvent varier de 15 % d’un jour à l’autre. Pour un suivi de la dégradation, ce n’est pas une faille, c’est une propriété. Une tendance lissée à la moyenne mobile est plus fiable que des mesures laboratoires isolées.
⚠️ Attention : Ne versionnez jamais vos clés d’API dans le dépôt. Placez-les dans une variable d’environnement ou un fichier
.envexclu de Git.
Le piège du dossier metrics/ sans Git
Stocker les fichiers localement c’est bien. Les perdre parce que vous avez effacé le mauvais dossier, c’est idiot. Versionnez le répertoire metrics/ dans un dépôt Git dédié, en faisant attention au volume. Un fichier JSON Lighthouse pèse entre 300 Ko et 600 Ko. À raison de 4 URLs par jour, vous atteignez 50 Mo par mois.
Un script de pruning qui ne garde que les 90 derniers jours règle le problème :
find metrics/ -name "*.json" -mtime +90 -delete
Le fichier CSV de résumé, lui, peut être conservé indéfiniment. Il pèse quelques kilo-octets par an. Vous aurez une série temporelle exploitable sans gonfler votre dépôt.
Cette approche rend l’historique opposable. Quand un prestataire vous dit que « le TTFB a toujours été autour de 800 ms », vous sortez le commit qui montre 320 ms il y a trois mois. Le dialogue change.
Détecter une régression en trois commandes shell
Voici le moment où le script montre sa valeur. Vous suspectez une régression après une mise en production d’hier. Vous voulez comparer les LCP d’avant et d’après.
# Dernière valeur de LCP pour l'URL critique
grep "produit-12" metrics/summary_2026-05-10.csv | tail -1 | cut -d',' -f3
# Valeur d'il y a deux jours
grep "produit-12" metrics/summary_2026-05-09.csv | tail -1 | cut -d',' -f3
Si l’écart dépasse 20 %, vous savez quoi regarder dans le JSON : l’audit network-requests pour traquer un nouveau script, ou l’audit resource-summary pour un CSS qui a gonflé.
Avec jq, vous pouvez même calculer la moyenne glissante sur 7 jours. Pas besoin d’un tableur. Le shell fait ça très bien :
awk -F, '/produit-12/ {sum+=$3; count++} END {print sum/count}' metrics/summary_*.csv
Ce type de vérification prend 30 secondes. Il ne remplace pas un monitoring temps réel, mais il couvre l’essentiel des cas où personne n’avait les yeux sur les dashboards au moment du déploiement.
Ce que le script voit mais pas le dashboard
Un cas vécu : un client avait un LCP qui oscillait entre 2s et 4s selon l’heure. Son outil de monitoring affichait une moyenne roulante propre, rien d’inquiétant. En ouvrant les JSON bruts, on a découvert que les pics coïncidaient avec une maintenance du cache d’un CDN périphérique. L’outil lissait cette variabilité critique. Les fichiers JSON ont permis d’isoler le jour exact où la configuration du CDN avait changé. Sans l’historique granulaire, on serait encore en train de chercher du côté du bundler JS.
Ce script est à vos métriques ce que Zustand est au state management React : léger, direct, sans magie. Il ne vous impose pas une architecture. Il répond à un besoin unique, sans couche d’abstraction inutile. Vous voulez changer le format de sortie vers Parquet ? Vous modifiez la boucle. Vous voulez ajouter la capture des INP via une extension ? Vous ajoutez une fonction. Personne ne vous retirera l’accès à vos propres fichiers.
Questions fréquentes
Ce script fonctionne-t-il avec les métriques de la Search Console ?
Non, la Search Console expose des données agrégées sur 28 jours, pas des mesures ponctuelles. Le script se branche sur l’API PageSpeed Insights, qui exécute un audit à la demande. Pour des données de terrain, il faut interroger l’API CrUX. Le principe de sauvegarde locale reste le même.
Peut-on remplacer l’API PageSpeed Insights par des tests locaux avec Lighthouse CLI ?
Oui, et c’est recommandé si vous avez besoin de mesures répétables sur une machine dédiée. lighthouse --output json --chrome-flags="--headless" produit le même JSON. Vous gagnez en contrôle sur les conditions réseau simulées, mais vous perdez la simplicité du curl. À intégrer dans le script si vous voulez une chaîne tout-automatisée.
Le script utilise une clé API Google, est-ce payant ?
L’API PageSpeed Insights a un quota gratuit standard sans clé. Si vous dépassez le quota, une clé API permet des quotas plus élevés, avec un coût à la requête au-delà d’un certain seuil. Le script de cet article fonctionne sans clé pour quelques URLs par jour. Renseignez-vous sur la tarification en vigueur avant d’industrialiser.