I Core Web Vitals sono tre metriche con cui Google misura l'esperienza di caricamento e interazione degli utenti reali. Non sono un punteggio estetico né un voto complessivo: sono tre numeri con soglie precise, e ciascuno peggiora per ragioni tecniche indipendenti dalle altre.

La conseguenza pratica è che «ottimizzare la velocità» come attività generica produce spesso poco. Serve sapere quale dei tre numeri è fuori soglia, perché gli interventi che risolvono l'uno non toccano gli altri — e in un caso li peggiorano.

Le soglie#

le tre metriche
LCP  Largest Contentful Paint     quando compare l'elemento più grande     ≤ 2,5 s  buono   ≤ 4,0 s  da migliorare   > 4,0 s  scarsoINP  Interaction to Next Paint    quanto tarda la risposta a un tocco     ≤ 200 ms buono   ≤ 500 ms da migliorare   > 500 ms scarsoCLS  Cumulative Layout Shift      quanto il contenuto si sposta da solo     ≤ 0,1    buono   ≤ 0,25   da migliorare   > 0,25   scarso
come viene letto il numero
Il dettaglio che cambia il modo di leggere questi numeri: lavalutazione non usa la MEDIA, usa il 75° PERCENTILE delle visitereali degli ultimi 28 giorni.  100 visite al vostro sito, ordinate dalla più veloce alla più lenta:  ┌────────────────────────────────────────────────────────┐  │ ██████████████████████████████████████████████ │ ████ │  │        75 visite sotto la soglia               │ 25   │  └────────────────────────────────────────────────────────┘                                      qui viene letto il valoreConseguenza pratica: provare il sito dal proprio portatile su fibramisura il 5° percentile. Il numero che conta viene dai telefoni difascia media su rete mobile, che nella maggior parte dei siti italianisono più della metà del traffico.

LCP: nell'ordine giusto#

le quattro cause, in ordine
Le cause dell'LCP, nell'ordine in cui vale la pena controllarle.Saltare l'ordine è il motivo per cui molte ottimizzazioni nonspostano niente.  1. Tempo di risposta del server (TTFB)     Se il server impiega 1,2 s a rispondere, l'LCP non può stare     sotto 2,5 s per quanto si ottimizzi il resto. Si guarda prima.  2. Risorse che bloccano il disegno     Un CSS o uno script sincrono nell'head ferma il parser: finché     non è scaricato e interpretato, non compare niente.  3. L'immagine principale scoperta tardi     Se l'elemento più grande è un'immagine caricata via CSS, o     marcata loading="lazy", il browser la richiede DOPO aver     calcolato il layout — cioè secondi più tardi del necessario.  4. I font     Se il testo resta invisibile in attesa del font, e il testo     è l'elemento più grande, l'LCP aspetta il font.

L'ordine non è un'opinione: è una catena di dipendenze. Ottimizzare le immagini quando il server impiega un secondo e due a rispondere è lavoro che non può produrre risultati, perché il collo di bottiglia è a monte.

pagina.htmlHTML
<!-- L'elemento LCP è l'unico che NON va mai in lazy loading, e     l'unico che vale la pena anticipare esplicitamente. --><!-- ✗ Il caso più comune: l'immagine di apertura in lazy.     Il browser la richiede dopo il layout, e l'LCP slitta di secondi. --><img src="/foto.webp" loading="lazy"><!-- ✓ Eager, priorità alta, dimensioni dichiarate (che servono anche     al CLS: lo spazio è riservato prima che l'immagine arrivi). --><img src="/foto.webp" width="760" height="506"     loading="eager" fetchpriority="high" decoding="async"     alt="…"><!-- ✓ I font usati sopra la piega: precaricati, così partono in     parallelo al CSS invece di aspettarne il download.     crossorigin è obbligatorio anche sullo stesso dominio. --><link rel="preload" href="/fonts/titolo.woff2" as="font"      type="font/woff2" crossorigin>

L'errore più diffuso è il quinto rigo: loading="lazy" applicato a tutte le immagini, compresa quella di apertura. Il caricamento pigro è un ottimo strumento per le immagini sotto la piega; applicato all'elemento LCP fa esattamente il contrario di quello che serve — il browser rimanda la richiesta a dopo il calcolo del layout, e la metrica peggiora di secondi.

I font, che tirano in due direzioni#

font.cssCSS
/* Il compromesso sui font, che ha effetti opposti su LCP e CLS. */@font-face {  font-family: 'Titolo';  src: url('/fonts/titolo.woff2') format('woff2');  /* swap: il testo compare SUBITO con il font di ripiego e viene     sostituito all'arrivo. Buono per LCP, rischioso per CLS se i     due font hanno metriche diverse (il testo "salta"). */  font-display: swap;  /* La correzione: allineare le metriche del font di ripiego a     quelle del definitivo, così la sostituzione non sposta niente. */  size-adjust: 97%;  ascent-override: 90%;  descent-override: 22%;}

È il caso in cui LCP e CLS confliggono. font-display: swap fa comparire subito il testo con il font di sistema, il che aiuta l'LCP; ma quando arriva il font definitivo il testo cambia dimensioni e la pagina si assesta, il che peggiora il CLS. Le proprietà di correzione delle metriche — size-adjust, ascent-override — allineano il font di ripiego a quello vero, e la sostituzione diventa invisibile.

INP: una sola causa#

inp.jsJavaScript
// INP ha una causa dominante sola: attività JavaScript lunghe sul// thread principale. Finché un compito occupa il thread, il browser// non può né rispondere al tocco né ridisegnare.// ✗ Un ciclo su 5000 elementi blocca tutto per centinaia di ms.function elabora(elementi) {  for (const e of elementi) calcolaPesante(e);}// ✓ Spezzarlo, restituendo il controllo al browser fra un blocco//   e l'altro. Se l'utente tocca qualcosa nel frattempo, il browser//   può rispondere.async function elabora(elementi) {  for (let i = 0; i < elementi.length; i += 50) {    for (const e of elementi.slice(i, i + 50)) calcolaPesante(e);    await new Promise(r => setTimeout(r, 0));  }}// ✓ E l'aggiornamento visivo prima del lavoro pesante: l'utente//   vede la reazione al proprio tocco entro un frame, il calcolo//   avviene dopo.bottone.addEventListener('click', async () => {  bottone.classList.add('in-corso');        // riscontro immediato  await new Promise(r => requestAnimationFrame(r));  await elabora(dati);                      // il lavoro vero});

L'INP misura il ritardo fra l'azione dell'utente e il primo aggiornamento visibile sullo schermo, considerando le interazioni peggiori della visita. La causa è quasi sempre la stessa: il thread principale è occupato, quindi il browser non può né eseguire il gestore dell'evento né ridisegnare.

La tecnica più efficace, e la meno intuitiva, è la seconda: dare un riscontro visivo prima di cominciare il lavoro. Aggiungere una classe che cambia l'aspetto del pulsante, aspettare un frame, e solo dopo eseguire il calcolo. L'utente vede una reazione entro sedici millisecondi; il fatto che il risultato arrivi dopo mezzo secondo è un'altra cosa, e viene percepita in modo completamente diverso.

CLS: riservare lo spazio prima#

cls.cssCSS
/* CLS: tutto ciò che occupa spazio deve dichiararlo PRIMA, non dopo. *//* ✗ Immagine senza dimensioni: il testo sotto salta quando arriva */img { width: 100%; }/* ✓ Le dimensioni nell'HTML (width/height) o il rapporto nel CSS:      il browser riserva lo spazio prima del download */img {  width: 100%;  height: auto;  aspect-ratio: 16 / 9;}/* ✗ Un avviso inserito in cima alla pagina spinge giù tutto il resto *//* ✓ Lo spazio è riservato anche quando l'avviso non c'è, oppure      l'avviso è sovrapposto invece che inserito nel flusso */.fascia-avvisi { min-height: 48px; }

Il CLS è la più facile delle tre da correggere e la più fastidiosa da subire: è la metrica che misura quante volte il contenuto si è spostato mentre l'utente stava leggendo — o, nel caso peggiore, mentre stava toccando un pulsante che nel frattempo si era mosso.

Laboratorio e campo non misurano la stessa cosa#

misura
# Laboratorio: diagnostica, isola le cause, e' riproducibile.npx lighthouse https://esempio.it --form-factor=mobile \>   --only-categories=performance --view# Campo: e' quello che conta davvero. Da Search Console# (Esperienza > Segnali web essenziali) oppure dall'API pubblica:curl -s "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$CHIAVE" \>   -H 'Content-Type: application/json' \>   -d '{"origin":"https://esempio.it","formFactor":"PHONE"}' \>   | jq '.record.metrics | {>       lcp: .largest_contentful_paint.percentiles.p75,>       inp: .interaction_to_next_paint.percentiles.p75,>       cls: .cumulative_layout_shift.percentiles.p75 }'{  "lcp": 3800,  "inp": 184,  "cls": "0.24"}# LCP e CLS fuori soglia, INP a posto: si sa gia' dove intervenire.
Lighthouse simula un dispositivo e una rete ipotetici: serve a diagnosticare. I dati di campo vengono dai browser reali, e sono quelli che contano nella valutazione.

Un punteggio Lighthouse quasi perfetto insieme a Core Web Vitals non superati sul campo è una combinazione normale, non un errore di misura: significa che gli utenti reali non assomigliano alla simulazione. Il caso tipico è un sito provato su un portatile veloce e visitato da telefoni di quattro anni fa.

Misurare sul proprio pubblico#

vitals.jsJavaScript
// Misurare sul proprio sito, sugli utenti veri, senza terze parti.// L'API è nativa nel browser: bastano poche righe.function segnala(metrica) {  navigator.sendBeacon('/api/vitals', JSON.stringify({    nome: metrica.name,          // 'LCP' | 'INP' | 'CLS'    valore: metrica.value,    percorso: location.pathname  }));}// Gli osservatori nativi, senza librerie:new PerformanceObserver((l) => {  const e = l.getEntries().at(-1);  segnala({ name: 'LCP', value: e.startTime });}).observe({ type: 'largest-contentful-paint', buffered: true });// I dati restano vostri, non serve consenso perché non c'è// alcun identificatore dell'utente, e vedete il 75° percentile// del VOSTRO pubblico senza aspettare 28 giorni.

Le API sono native nel browser: si possono raccogliere i tre numeri sugli utenti reali senza alcun servizio di terze parti, senza cookie e senza identificatori — quindi senza le implicazioni discusse nell'articolo su cookie e GDPR. Il vantaggio pratico è che si vede l'effetto di una modifica subito, invece di aspettare che la finestra di 28 giorni si aggiorni.

L'ordine degli interventi#

MetricaPrima causa da controllareIntervento più efficace
LCPtempo di risposta del servercache lato server, poi risorse bloccanti
LCPimmagine principale in lazyeager + fetchpriority="high"
LCPCSS bloccante nell'headun solo foglio, minificato, senza importazioni a catena
INPcompiti JavaScript lunghispezzarli, e riscontro visivo prima del lavoro
INPgestori che leggono il layoutevitare letture che forzano ricalcoli sincroni
CLSimmagini senza dimensioniwidth/height o aspect-ratio
CLSfont con metriche diversesize-adjust sul font di ripiego
CLSavvisi inseriti in cimariservare lo spazio, o sovrapporre

Una nota conclusiva su cosa aspettarsi: i Core Web Vitals entrano nella valutazione dell'esperienza di pagina, ma non sostituiscono la pertinenza del contenuto. Una pagina velocissima che risponde male alla domanda dell'utente non si posiziona; una pagina lenta con il contenuto migliore parte svantaggiata a parità di tutto il resto. Sono un fattore fra i tanti — con la particolarità di essere uno dei pochi interamente sotto il vostro controllo e verificabile con un numero.