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

Migliorare le metriche LCP con i Web Workers e il caricamento delle immagini a 2 fasi
Il più delle volte un elemento immagine grande nel viewport visibile diventa 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 è usare tattiche più avanzate come il caricamento a 2 fasi e il threading della pagina con i Web Workers per liberare risorse sul main thread.

Ultima revisione di Arjen Karel a marzo 2026
Un po' di contesto
Mi occupo di page speed e il mio sito web è la mia vetrina. Sulla mia homepage dichiaro con orgoglio che il mio sito è il più veloce del mondo. Ecco perché ho bisogno che la mia pagina si carichi il più velocemente possibile e di spremere fino all'ultima goccia di page speed dal mio sito.
Le tecniche che ti mostrerò oggi potrebbero non essere praticabili per un comune sito (WordPress) senza il supporto di un team di sviluppo dedicato e qualificato. Se non puoi replicare questa tecnica sul tuo sito, ti incoraggio comunque a leggere l'articolo per capire come penso alla page speed e quali sono le mie considerazioni.
Il problema: immagini grandi nel viewport visibile
Un'immagine grande nel viewport visibile diventa spesso l'elemento Largest Contentful Paint. Succede di frequente che questa immagine LCP non superi i Core Web Vitals. Vedo risultati come questo ogni giorno.

Ci sono diversi modi per far sì che questo elemento appaia sullo schermo velocemente:
- Precarica l'elemento LCP. Il precaricamento dell'immagine LCP farà in modo che l'immagine sia disponibile al browser il prima possibile. Combinalo con
fetchpriority="high"per indicare al browser di dare priorità a questa immagine rispetto alle altre risorse. - Usa immagini responsive. Assicurati di non servire immagini di dimensioni desktop ai dispositivi mobili.
- Comprimi le immagini. La compressione delle immagini può ridurre drasticamente il peso dell'immagine.
- Usa formati d'immagine di nuova generazione. I formati d'immagine di nuova generazione come WebP superano i vecchi formati come JPEG e PNG in quasi tutti i casi.
- Minimizza il percorso di rendering critico. Elimina tutte le risorse render blocking come JavaScript e fogli di stile che potrebbero ritardare il LCP.
Purtroppo, nonostante tutte queste ottimizzazioni, in alcuni casi le metriche LCP potrebbero ancora non superare l'audit dei Core Web Vitals. Perché? Il peso dell'immagine da solo è sufficiente a ritardare la fase di resource load duration del LCP.
La soluzione: caricamento a 2 fasi e Web Workers
La soluzione che ho implementato (dopo aver ottimizzato tutti gli altri problemi sul mio sito) è il caricamento delle immagini a 2 fasi.
L'idea è semplice: al primo rendering mostra un'immagine a bassa qualità con le stesse identiche dimensioni dell'immagine finale ad alta qualità. Subito dopo la visualizzazione di quell'immagine, avvia il processo che sostituisce l'immagine a bassa qualità con quella ad alta qualità.
Un'implementazione di base potrebbe essere così: prima aggiungi un event listener per l'evento load a un'immagine. Quando l'immagine viene caricata, l'event listener si rimuove da solo e il src dell'immagine viene sostituito con quello dell'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 a bassa qualità 3-5kb 
Fase 2: WebP ad alta qualità 20-40kb 
Può sembrare semplice (e lo è), ma sostituire molte immagini all'inizio del processo di rendering causerà troppa attività sul main thread, influenzando negativamente le altre metriche dei Core Web Vitals.
Ecco perché ho scelto di spostare parte del lavoro su un Web Worker. Un Web Worker viene eseguito in un thread separato e non ha un vero accesso alla pagina corrente. La comunicazione tra il Web Worker e la pagina avviene tramite un sistema di messaggistica. Il vantaggio evidente è che non usiamo il main thread della pagina, liberando risorse lì. Lo svantaggio è che l'uso di un Web Worker può essere un po' macchinoso.
Il processo in sé non è difficile. Una volta attivato l'evento DOMContentLoaded, raccolgo tutte le immagini nella pagina. Se un'immagine è già stata caricata, la sostituisco immediatamente. Se non è ancora caricata (perché potrebbe essere in lazy loading), aggiungo un event listener che la sostituisce dopo il lazy loading.
Un avvertimento importante: il browser considera ogni sostituzione di immagine come un nuovo candidato LCP. Se la sostituzione con l'immagine ad alta qualità avviene dopo 2,5 secondi, il LCP verrà misurato al momento del cambio, non a quello dell'immagine segnaposto. Ecco perché è importante che il Web Worker recuperi e sostituisca l'immagine il più rapidamente possibile.
Il risultato: spettacolare.

Il codice per il caricamento LCP a 2 fasi tramite Web Worker
Ecco il codice che uso per velocizzare il mio LCP tramite il caricamento a 2 fasi e un Web Worker. Il codice sulla pagina principale chiama un Web Worker che scaricherà le immagini. Il Web Worker passa il risultato sotto forma di 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()
// Invia i dati dell'immagine al thread UI!
self.postMessage({
uid: event.data.uid,
blob: blob,
})
}) Script.js
Lo script.js viene eseguito come un normale script sulla pagina web attiva. Per prima cosa carica il worker. Poi scorre tutte le immagini della pagina. Questo avviene all'inizio del processo di rendering. Un'immagine potrebbe essere già caricata o meno. Se l'immagine a bassa qualità è già caricata, avvia immediatamente il processo di sostituzione. Se non è ancora caricata, aggiunge un listener all'evento load dell'immagine per avviare la sostituzione non appena questa viene caricata.
Al caricamento dell'immagine viene generato un ID univoco. Questo mi permette di ritrovarla facilmente nella pagina (ricorda che il worker non ha accesso al DOM, quindi non posso inviargli il nodo DOM dell'immagine). L'URL dell'immagine e l'ID univoco vengono quindi inviati al worker. Dopo aver recuperato l'immagine, il worker la rimanda allo script sotto forma di blob. Infine, lo script sostituisce il vecchio URL dell'immagine con l'URL del blob creato dal Web Worker.
var myWorker = new Worker('/path-to/worker.js');
// invia un messaggio al worker
const sendMessage = (img) => {
// l'uid rende più facile trovare l'immagine
var uid = create_UID();
// imposta data-uid sull'elemento immagine
img.dataset.uid = uid;
// invia il messaggio al worker
myWorker.postMessage({ src: img.src, uid: uid });
};
// genera 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;
}
// quando riceviamo un risultato dal worker
myWorker.addEventListener('message', event => {
// recupera i dati del messaggio dall'evento
const imageData = event.data
// ottieni l'elemento originale per questa immagine
const imageElement = document.querySelectorAll("img[data-uid='" + imageData.uid + "']");
// possiamo usare il Blob come sorgente dell'immagine! Dobbiamo solo
// prima convertirlo in un object URL
const objectURL = URL.createObjectURL(imageData.blob)
// una volta caricata l'immagine, faremo un po' di pulizia
imageElement.onload = () => {
URL.revokeObjectURL(objectURL)
}
imageElement[0].setAttribute('src', objectURL)
})
// recupera tutte le immagini
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('img[loading="lazy"]').forEach(
img => {
// l'immagine è già visibile?
img.complete ?
// esegui la sostituzione immediatamente
sendMessage(img) :
// esegui la sostituzione al caricamento
img.addEventListener(
"load", i => { sendMessage(img) }, { once: true }
)
})
}) Per verificare il miglioramento del LCP sul campo, usa il Real User Monitoring per monitorare l'esperienza dei tuoi visitatori reali sulla pagina. Gli strumenti di laboratorio come Lighthouse mostreranno il miglioramento, ma i field data degli utenti reali su connessioni variabili sono ciò che conta per superare i Core Web Vitals.
Dove sei lento davvero.
Ti mappo il critical rendering path con dati RUM. Ricevi una lista di fix in ordine di priorità, non un report di Lighthouse.
Richiedi l'audit
