Come ho ridotto il mio LCP del 70%
Scopri metodi avanzati per migliorare i Core Web Vitals

Migliorare le metriche LCP con web worker e caricamento delle immagini in 2 fasi
Nella maggior parte dei casi, un elemento immagine di grandi dimensioni nel viewport visibile diventerà l'elemento Largest Contentful Paint. Anche dopo aver applicato tutte le best practice di Lighthouse come il ridimensionamento delle immagini, la compressione delle immagini, la conversione in WebP e il precaricamento dell'elemento LCP, il tuo Largest Contentful Paint potrebbe comunque non superare i Core Web Vitals.
L'unico modo per risolvere questo problema è utilizzare tattiche più avanzate come il caricamento in 2 fasi e il threading della pagina con i web worker per liberare risorse sul main thread.
In questo articolo, mostrerò come migliorare ulteriormente il Largest Contentful Paint.

Un po' di contesto
Sono un esperto di pagespeed e il mio sito web è il mio biglietto da visita. Nella mia homepage dichiaro con orgoglio che il mio sito è il più veloce al mondo. Ecco perché ho bisogno che la mia pagina si carichi il più velocemente possibile e sprema ogni goccia di pagespeed dal mio sito.
Le tecniche che vi mostrerò oggi potrebbero non essere praticabili per un sito medio (WordPress) senza il supporto di un team di sviluppo dedicato e talentuoso. Se non riuscite a replicare questa tecnica sul vostro sito, vi incoraggio comunque a leggere l'articolo e scoprire come ragiono sul pagespeed e quali sono le mie considerazioni.
Il problema: immagini di grandi dimensioni nel viewport visibile
Un'immagine di grandi dimensioni nel viewport visibile diventerà spesso l'elemento Largest Contentful Paint. Capita spesso che questa immagine LCP non superi i Core Web Vitals. Vedo risultati come questi quotidianamente.

Ci sono diversi modi per assicurarsi che questo elemento appaia rapidamente sullo schermo:
- Precaricare l'elemento LCP. Il precaricamento dell'immagine LCP assicurerà che questa immagine sia disponibile per il browser il prima possibile.
- Utilizzare immagini responsive. Assicurati di non servire immagini dimensionate per il desktop ai dispositivi mobili.
- Comprimi le tue immagini. La compressione delle immagini potrebbe ridurre drasticamente le dimensioni dell'immagine
- Usa formati immagine di nuova generazione. I formati immagine di nuova generazione come WebP superano i formati più vecchi come JPEG e PNG in quasi tutti i casi.
- Minimizza il critical rendering path. Elimina tutte le risorse che bloccano il rendering come JavaScript e fogli di stile che potrebbero ritardare il LCP.
Purtroppo, nonostante tutte queste ottimizzazioni, in alcuni casi le metriche LCP potrebbero comunque non superare l'audit dei Core Web Vitals. Perché? Le dimensioni dell'immagine da sole sono sufficienti a ritardare il LCP.
La soluzione: caricamento in 2 fasi e web worker
La soluzione che ho implementato (dopo aver ottimizzato tutti gli altri problemi sul mio sito) è il caricamento delle immagini in 2 fasi.
L'idea è semplice: al primo rendering mostra un'immagine di bassa qualità con le stesse identiche dimensioni dell'immagine finale ad alta qualità. Immediatamente dopo che quell'immagine viene visualizzata, avvia il processo che sostituisce l'immagine di bassa qualità con un'immagine ad alta qualità.
Un'implementazione molto basilare potrebbe apparire così: prima aggiungi un event listener di caricamento a un'immagine. Quando l'immagine si carica, lo stesso event listener si rimuove e il src dell'immagine viene sostituito con l'immagine finale ad alta qualità.
<img
width="100"
height="100"
alt="some alt text"
src="lq.webp"
onload="this.onload=null;this.src='hq.webp'"
> Fase 1: webp di bassa qualità 3-5kb 
Fase 2: webp di alta qualità 20-40kb 
Questo potrebbe sembrare abbastanza semplice (e lo è), ma sostituire un gran numero di immagini nelle fasi iniziali del processo di rendering causerà troppa attività sul main thread e influenzerà altre metriche dei Core Web Vitals.
Ecco perché ho scelto di delegare parte del lavoro a un web worker. Un web worker viene eseguito in un nuovo thread e non ha accesso diretto alla pagina corrente. La comunicazione tra il web worker e la pagina avviene attraverso un sistema di messaggistica. Il vantaggio evidente è che non stiamo utilizzando il main thread della pagina, liberando risorse. Lo svantaggio è che utilizzare un web worker può essere un po' macchinoso.
Il processo in sé non è così difficile. Una volta che l'evento DomContentLoaded è stato attivato, raccolgo tutte le immagini sulla pagina. Se un'immagine è già stata caricata, la sostituisco immediatamente. Se non è stata caricata (perché l'immagine potrebbe essere in lazy load), allego un event listener che sostituisce l'immagine dopo il lazy load.
Il risultato: spettacolare

Il codice per il caricamento LCP in 2 fasi tramite un web worker
Ecco il codice che utilizzo per velocizzare il mio LCP tramite il caricamento in 2 fasi e un web worker. Il codice sulla pagina principale chiama un web worker che recupererà le immagini. Il web worker passa il risultato come blob alla pagina principale. Alla ricezione del blob, l'immagine viene sostituita.
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()
// Send the image data to the UI thread!
self.postMessage({
uid: event.data.uid,
blob: blob,
})
}) Script.js
Lo script.js verrà eseguito come un normale script sulla pagina web attiva. Lo script carica prima il worker. Poi ciclerà attraverso tutte le immagini della pagina. Questo avviene nelle fasi iniziali del processo di rendering. Un'immagine potrebbe essere già caricata oppure no. Se un'immagine di bassa qualità è già caricata, avvierà immediatamente il processo di sostituzione. Se non è ancora caricata, allegherà un listener all'evento di caricamento dell'immagine che avvia il processo di sostituzione non appena l'immagine viene caricata..
Quando un'immagine viene caricata, viene generato un ID univoco per quell'immagine. Questo mi permette di ritrovare facilmente l'immagine sulla pagina (ricorda, il worker non ha accesso al DOM quindi non posso inviare il nodo DOM dell'immagine).
L'URL dell'immagine e l'ID univoco vengono quindi inviati al worker.
Quando il worker ha recuperato l'immagine, viene rinviata allo script come blob. Lo script alla fine sostituisce il vecchio URL dell'immagine con l'URL del blob creato dal web worker.
var myWorker = new Worker('/path-to/worker.js');
// send a message to worker
const sendMessage = (img) => {
// uid makes it easier to find the image
var uid = create_UID();
// set data-uid on image element
img.dataset.uid = uid;
// send message to worker
myWorker.postMessage({ src: img.src, uid: uid });
};
// generate the 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;
}
// when we get a result from the worker
myWorker.addEventListener('message', event => {
// Grab the message data from the event
const imageData = event.data
// Get the original element for this image
const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");
// We can use the `Blob` as an image source! We just need to convert it
// to an object URL first
const objectURL = URL.createObjectURL(imageData.blob)
// Once the image is loaded, we'll want to do some extra cleanup
imageElement.onload = () => {
URL.revokeObjectURL(objectURL)
}
imageElement[0].setAttribute('src', objectURL)
})
// get all images
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('img[loading="lazy"]').forEach(
img => {
// image is already visible?
img.complete ?
// swap immediately
sendMessage(img) :
// swap on load
img.addEventListener(
"load", i => { sendMessage(img) }, { once: true }
)
})
}) Punteggio Core Web Vitals con immagine LCP precaricata
Stop debating in Jira.
Get a definitive answer on your performance issues. I deliver a granular breakdown of your critical rendering path.
- Definitive Answers
- Granular Breakdown
- Critical Path Analysis

