Comment j'ai réduit mon LCP de 70 %

Apprenez des méthodes avancées pour améliorer les Core Web Vitals

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-03-04

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.

Pourquoi devrais-je précharger l'image du largest contentful paint

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.

mauvais LCP avec une grande image

Il existe several façons de s'assurer que cet élément apparaisse rapidement à l'écran :

  1. [url=\/pagespeed\/preload-largest-contentful-paint-image]Précharger l'élément LCP[\/url]. Le préchargement de l'image LCP garantira que cette image est disponible pour le navigateur le plus tôt possible. Combinez cela avec fetchpriority="high" pour dire au navigateur de prioriser cette image par rapport aux autres ressources.
  2. Utiliser des images responsives. Assurez-vous de ne pas servir d'images de taille bureau aux appareils mobiles.
  3. [url=\/pagespeed\/optimize-images-for-core-web-vitals]Compresser vos images[\/url]. La compression d'images peut réduire considérablement la taille de l'image.
  4. Utiliser des formats d'image de nouvelle génération. Les formats d'image de nouvelle génération comme WebP surpassent les anciens formats comme JPEG et PNG dans presque tous les cas.
  5. [url=\/pagespeed\/fix-eliminate-render-blocking-resources]Minimiser le chemin critique du rendu[\/url]. Éliminez toutes les ressources bloquant le rendu comme le JavaScript et les feuilles de style qui pourraient retarder le LCP.

    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 : le chargement en 2 étapes et les Web Workers

    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é.

    <img
         width="100"
         height="100"
         alt="un texte alternatif"
         src="lq.webp"
         onload="this.onload=null;this.src='hq.webp'"
    >
                

    É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.

    Le code pour le chargement LCP en 2 étapes via un Web Worker

    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.

    Worker.js
    Le worker a un seul rôle. Il écoute les messages. Un message contient l'URL d'une image et un identifiant unique. Tout d'abord, il transforme l'URL de l'image vers la version haute qualité. Dans mon cas, en remplaçant /lq par /resize dans l'URL de l'image. Le worker va ensuite récupérer l'image haute qualité, créer un blob et renvoyer le blob de l'image accompagné de l'identifiant unique.
    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

    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.

    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 }
                    )
            })
    })
                

    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]

    [include]sidebar.html[\/include] [include]blogfooter.html[\/include]
    Comment j'ai réduit mon LCP de 70 %Core Web Vitals Comment j'ai réduit mon LCP de 70 %