| name | dev-web-performance-optimizer |
| description | Optimise les performances web (Core Web Vitals, loading, rendering). Se déclenche avec "performance web", "Core Web Vitals", "LCP", "FID", "CLS", "lighthouse score", "page speed", "site lent", "bundle size", "lazy loading". Also triggers on "make my site faster", "LCP and CLS issues". |
Web Performance Optimizer
Étape 1 — Mesurer d'abord
Règle absolue : jamais d'optimisation sans baseline mesurée.
npx lighthouse https://example.com --output=json --output-path=./lh-report.json \
--form-factor=mobile --throttling-method=simulate
npx crux-api --origin https://example.com --key YOUR_API_KEY
npx webpagetest test https://example.com --key YOUR_KEY --location ec2-eu-west-1
Collecter avant et après chaque changement. Utiliser CrUX (données terrain) et non seulement Lighthouse (labo).
Étape 2 — Diagnostiquer par métrique
Seuils "Good" 2026
| Métrique | Good | Needs Improvement | Poor |
|---|
| LCP | < 2.5 s | 2.5 – 4 s | > 4 s |
| INP | < 200 ms | 200 – 500 ms | > 500 ms |
| CLS | < 0.1 | 0.1 – 0.25 | > 0.25 |
| TTFB | < 800 ms | 800 ms – 1.8 s | > 1.8 s |
| FCP | < 1.8 s | 1.8 – 3 s | > 3 s |
FID est remplacé par INP depuis mars 2024. Ne pas cibler FID dans les nouveaux rapports.
Arbre de décision rapide
- LCP élevé → image LCP non preloadée ? TTFB élevé ? police bloquante ? → voir Étape 3
- INP élevé → main thread bloqué ? long tasks (> 50 ms) ? → voir Étape 5
- CLS élevé → images/vidéos sans dimensions ? fonts swap ? iframes dynamiques ? → voir Étape 6
- TTFB élevé → problème serveur/CDN/DB → résoudre côté infra avant tout le reste
Étape 3 — Optimiser le chargement des ressources critiques
Images
<link rel="preload" as="image" href="/hero.avif"
fetchpriority="high" type="image/avif">
<img src="photo.avif" srcset="photo-400.avif 400w, photo-800.avif 800w"
sizes="(max-width: 600px) 400px, 800px"
loading="lazy" decoding="async" width="800" height="600" alt="...">
npx sharp -i input.jpg -o output.avif --format avif --quality 60
npx sharp -i input.jpg -o output.webp --format webp --quality 75
Critères de format : AVIF = meilleure compression (–50 % vs JPEG), mais encodage lent → générer au build. WebP = fallback universel.
Fonts
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="font" href="/fonts/inter.woff2"
type="font/woff2" crossorigin>
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-display: swap;
}
Anti-pattern : font-display: block = invisible jusqu'à 3 s → toujours swap ou optional.
Étape 4 — Optimiser JavaScript (bundle)
npx webpack-bundle-analyzer stats.json
npx vite-bundle-visualizer
npx bundlephobia-cli react-pdf
const Dashboard = lazy(() => import('./Dashboard'));
import debounce from 'lodash/debounce';
<script src="analytics.js" defer></script>
<script src="chat.js" async></script>
Critères : defer = exécuté après parsing HTML ; async = exécuté dès téléchargé (peut bloquer). Préférer defer dans 90 % des cas.
Étape 5 — Réduire le blocage du main thread (INP)
async function processItems(items) {
for (const item of items) {
await scheduler.yield();
process(item);
}
}
const worker = new Worker('./heavy-computation.js', { type: 'module' });
worker.postMessage({ data: bigArray });
worker.onmessage = ({ data }) => updateUI(data);
input.addEventListener('input', debounce(handleSearch, 150));
elements.forEach(el => { el.style.width = el.offsetWidth + 'px'; });
const widths = elements.map(el => el.offsetWidth);
elements.forEach((el, i) => { el.style.width = widths[i] + 'px'; });
Étape 6 — Corriger le CLS
img, video { aspect-ratio: 16 / 9; width: 100%; }
.skeleton { background: #e0e0e0; min-height: 200px; border-radius: 4px; }
Causes fréquentes de CLS :
- Images sans
width/height explicites
- Ads ou embeds injectés sans espace réservé
- Fonts qui swappent et changent la hauteur de ligne
- Animations qui modifient
top/left (utiliser transform à la place)
Étape 7 — Cache et livraison
# Cache immuable pour assets versionnés (hash dans le nom de fichier)
location ~* \.(js|css|woff2|avif|webp)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Pages HTML : revalidation rapide
location / {
add_header Cache-Control "public, max-age=0, must-revalidate";
}
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'script') {
event.respondWith(
caches.match(event.request).then(r => r || fetch(event.request))
);
}
});
Checklist infra :
Étape 8 — CSS critique
npx critical index.html --base dist/ --inline --width 1300 --height 900
npx purgecss --css dist/styles.css --content dist/**/*.html dist/**/*.js \
--output dist/styles.purged.css
<link rel="preload" href="/styles.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
Anti-patterns et pièges
| Piège | Problème | Solution |
|---|
| Optimiser sans mesurer | Gains imaginaires, régressions réelles | Baseline CrUX/Lighthouse systématique |
loading="lazy" sur l'image LCP | LCP explose car téléchargement retardé | fetchpriority="high" + pas de lazy sur LCP |
async sur scripts qui lisent le DOM | Race condition, erreurs runtime | defer par défaut |
| Inliner tout le CSS | Impossible à mettre en cache | Inliner seulement le CSS critique (< 14 KB) |
| Compresser en gzip quand Brotli dispo | –15 % de ratio perdus | Brotli level 6 en production |
| Ignorer les données CrUX terrain | Lighthouse labo ≠ expérience réelle | Toujours valider avec CrUX API ou Search Console |
Font font-display: block | FOIT 3 s pour les utilisateurs lents | swap ou optional |
Règles de priorité
- TTFB d'abord : si > 800 ms, toute optimisation frontend est marginale.
- LCP image : précharger, dimensionner, AVIF — gain le plus rapide et le plus visible.
- JS inutile : tree shaking + code splitting avant toute micro-optimisation.
- Mesurer en terrain : CrUX + RUM (Real User Monitoring) prime sur Lighthouse labo.
- Ne pas sur-optimiser : un score Lighthouse 90+ en mobile est une cible saine ; passer de 95 à 100 a peu d'impact réel.
Communication Rules — MANDATORY
- Ultra-concise. No filler, no preamble, no pleasantries.
- Never say "happy to help", "sure!", "great question", "let me", or similar.
- Tool first, talk second. Act before explaining.
- Result first. Lead with outcome, not process.
- Stop when done. No summary, no recap, no trailing commentary.
- No politeness wrappers. Direct and blunt.
- Minimum words. If one word works, do not use ten.
- No unsolicited explanations.
- No emoji unless asked.