Comment j'ai réduit mon LCP de 70 %
Apprenez des méthodes avancées pour améliorer les Core Web Vitals

Améliorer les métriques LCP avec les Web Workers et le chargement d'images en 2 étapes
La plupart du temps, un grand élément d'image dans le viewport visible deviendra l'élément Largest Contentful Paint. Même après avoir appliqué toutes les bonnes pratiques de Lighthouse comme le redimensionnement des images, la compression, la conversion au format WebP et le préchargement de l'élément LCP, votre [url=\/core-web-vitals\/largest-contentful-paint]Largest Contentful Paint (LCP)[\/url] pourrait toujours ne pas passer les Core Web Vitals.
La seule façon de corriger cela est d'utiliser des tactiques plus avancées comme le chargement en 2 étapes et l'utilisation de Web Workers pour libérer des ressources sur le thread principal.

Dernière révision par [url=https:\/\/www.linkedin.com\/in\/arjenkarel\/]Arjen Karel[\/url] en mars 2026
Un peu de contexte
Je suis un spécialiste de la vitesse de page et mon site web est ma vitrine. Sur ma page d'accueil, je proclame fièrement que mon site est le plus rapide au monde. C'est pourquoi j'ai besoin que ma page se charge aussi vite que possible et que j'extraie chaque goutte de performance de mon site.
Les techniques que je vais vous montrer aujourd'hui pourraient ne pas être viables pour un site (WordPress) moyen sans le soutien d'une équipe de développement dédiée et talentueuse. Si vous ne pouvez pas reproduire cette technique sur votre propre site, je vous encourage tout de même à lire l'article pour apprendre comment je réfléchis à la vitesse de page et quelles sont mes considérations.
Le problème : de grandes images dans le viewport visible
Une grande image dans le viewport visible deviendra souvent l'élément Largest Contentful Paint. Il arrive fréquemment que cette image LCP ne passe pas les Core Web Vitals. Je vois des résultats comme celui-ci quotidiennement.
Il existe several façons de s'assurer que cet élément apparaisse rapidement à l'écran :
Malheureusement, malgré toutes ces optimisations, dans certains cas, les métriques LCP pourraient encore ne pas passer l'audit des Core Web Vitals. Pourquoi ? La taille de l'image seule suffit à retarder la phase de [url=\/core-web-vitals\/largest-contentful-paint\/resource-load-duration]durée de chargement de la ressource[\/url] du LCP.
La solution que j'ai mise en œuvre (après avoir optimisé tous les autres problèmes sur mon site) est le chargement d'images en 2 étapes.
L'idée est simple : au premier rendu, afficher une image de basse qualité ayant exactement les mêmes dimensions que l'image finale de haute qualité. Immédiatement après l'affichage de cette image, lancez le processus qui remplace l'image de basse qualité par une image de haute qualité.
Une implémentation très basique pourrait ressembler à ceci : tout d'abord, ajoutez un écouteur d'événement de chargement à une image. Lorsque l'image se charge, cet écouteur se détache et la source (src) de l'image est remplacée par l'image finale de haute qualité.
Étape 1 : WebP basse qualité 3-5 ko
Étape 2 : WebP haute qualité 20-40 ko
Cela peut paraître assez simple (et ça l'est), mais remplacer un grand nombre d'images au début du processus de rendu provoquera trop d'activité sur le thread principal et affectera d'autres métriques Core Web Vitals.
C'est pourquoi j'ai choisi de décharger une partie du travail sur un Web Worker. Un Web Worker s'exécute dans un nouveau thread et n'a pas d'accès réel à la page actuelle. La communication entre le Web Worker et la page se fait via un système de messagerie. L'avantage évident est que nous n'utilisons pas le thread principal de la page lui-même ; nous y libérons des ressources. L'inconvénient est que l'utilisation d'un Web Worker peut être un peu laborieuse.
Le processus lui-même n'est pas si difficile. Une fois l'événement DOMContentLoaded déclenché, je collecte toutes les images de la page. Si une image est déjà chargée, je la remplace immédiatement. Si elle n'est pas encore chargée (parce qu'elle pourrait être en lazy loading), je lui attache un écouteur d'événement qui remplace l'image après son chargement.
Une mise en garde importante : le navigateur traite chaque remplacement d'image comme un nouveau candidat LCP. Si votre remplacement d'image haute qualité intervient après 2,5 secondes, le LCP sera mesuré au moment du remplacement, et non au moment de l'image de substitution. C'est pourquoi il est crucial que le Web Worker récupère et remplace l'image aussi vite que possible.
Le résultat : spectaculaire.
Voici le code que j'utilise pour accélérer mon LCP grâce au chargement en 2 étapes et à un Web Worker. Le code sur la page principale appelle un Web Worker qui va récupérer les images. Le Web Worker transmet le résultat sous forme de blob à la page principale. À la réception du blob, l'image est remplacée.
Le fichier script.js s'exécutera comme un script normal sur la page web active. Le script charge d'abord le worker. Ensuite, il parcourt toutes les images d'une page. Cela se produit tôt dans le processus de rendu. Une image peut déjà être chargée ou non. Si une image de basse qualité est déjà chargée, il appelle immédiatement le processus de remplacement. Si elle n'est pas encore chargée, il attache un écouteur à l'événement de chargement de l'image qui lance le processus de remplacement dès que cette image est chargée.
Lorsqu'une image est chargée, un identifiant unique est généré pour celle-ci. Cela me permet de retrouver facilement l'image sur la page (n'oubliez pas que le worker n'a pas accès au DOM, donc je ne peux pas lui envoyer le nœud DOM de l'image). L'URL de l'image et l'identifiant unique sont ensuite envoyés au worker. Une fois que le worker a récupéré l'image, elle est renvoyée au script sous forme de blob. Le script finit par remplacer l'ancienne URL de l'image par l'URL du blob créée par le Web Worker.
Pour vérifier l'amélioration du LCP sur le terrain, utilisez le [url=https:\/\/coredash.app]Real User Monitoring (RUM)[\/url] pour suivre l'expérience réelle de vos visiteurs sur la page. Les outils de laboratoire comme Lighthouse montreront l'amélioration, mais ce sont les données de terrain provenant d'utilisateurs réels sur des connexions variées qui comptent pour réussir les Core Web Vitals.
[include]cwv\/authorbio.html[\/include]

fetchpriority="high" pour dire au navigateur de prioriser cette image par rapport aux autres ressources.
La solution : le chargement en 2 étapes et les Web Workers
<img
width="100"
height="100"
alt="un texte alternatif"
src="lq.webp"
onload="this.onload=null;this.src='hq.webp'"
>



Le code pour le chargement LCP en 2 étapes via un Web Worker
Worker.js
self.addEventListener('message', async event => {
const newimageURL = event.data.src.replace("/lq-","\/resize-");
const response = await fetch(newimageURL)
const blob = await response.blob()
\/\/ Envoyez les données de l'image au thread UI !
self.postMessage({
uid: event.data.uid,
blob: blob,
})
})
Script.js
var myWorker = new Worker('\/path-to\/worker.js');
\/\/ envoyez un message au worker
const sendMessage = (img) => {
\/\/ l'uid facilite la recherche de l'image
var uid = create_UID();
\/\/ définissez data-uid sur l'élément image
img.dataset.uid = uid;
\/\/ envoyez le message au worker
myWorker.postMessage({ src: img.src, uid: uid });
};
\/\/ générez l'uid
const create_UID = () => {
var dt = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(\/[xy]\/g, function(c) {
var r = (new Date().getTime() + Math.random() * 16) % 16 | 0;
dt = Math.floor(dt \/ 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uid;
}
\/\/ quand nous recevons un résultat du worker
myWorker.addEventListener('message', event => {
\/\/ Récupérez les données du message depuis l'événement
const imageData = event.data
\/\/ Récupérez l'élément d'origine pour cette image
const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");
\/\/ Nous pouvons utiliser le Blob comme source d'image ! Nous devons juste le convertir
\/\/ en une URL d'objet d'abord
const objectURL = URL.createObjectURL(imageData.blob)
\/\/ Une fois l'image chargée, nous voudrons effectuer un nettoyage supplémentaire
imageElement.onload = () => {
URL.revokeObjectURL(objectURL)
}
imageElement[0].setAttribute('src', objectURL)
})
\/\/ récupérez toutes les images
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('img[loading="lazy"]').forEach(
img => {
\/\/ l'image est déjà visible ?
img.complete ?
\/\/ remplacez immédiatement
sendMessage(img) :
\/\/ remplacez au chargement
img.addEventListener(
"load", i => { sendMessage(img) }, { once: true }
)
})
})

