Perché il CLS È il Core Web Vital Più Sottovalutato
Hai presente quando stai per cliccare su un pulsante e improvvisamente tutto si sposta? Finisci per cliccare su un banner pubblicitario, un link sbagliato o peggio — confermi un acquisto che non volevi fare. Ecco, quella frustrazione ha un nome tecnico: Cumulative Layout Shift (CLS). E no, non sei l'unico a cui capita.
Dopo aver trattato l'ottimizzazione di LCP e INP nei precedenti articoli, era inevitabile completare la trilogia dei Core Web Vitals con il CLS — la metrica che misura la stabilità visiva della tua pagina. Nel 2026 è più importante che mai: Google utilizza i dati CrUX (Chrome User Experience Report) al 75° percentile per valutare il tuo sito, e un CLS superiore a 0.1 ti penalizza sia nel ranking che nelle risposte AI di Gemini.
Ecco il punto: a differenza di LCP e INP, che sono metriche temporali misurate in millisecondi, il CLS è un valore adimensionale. E questo lo rende parecchio difficile da interpretare. Molti sviluppatori (me compreso, all'inizio) lo ignorano finché non vedono il semaforo rosso su PageSpeed Insights.
In questa guida vedremo come funziona davvero il calcolo, identificheremo le cause più comuni con esempi di codice funzionanti, e applicheremo tecniche avanzate come CSS Containment e font metric overrides per portare il CLS a zero. Niente teoria astratta — solo roba che puoi copiare e incollare nel tuo progetto.
Come Viene Calcolato il CLS: La Formula Che Devi Conoscere
Per ottimizzare qualcosa, devi prima capire come viene misurato. Il CLS non è un singolo valore statico — è il risultato della raffica più grande (largest burst) di layout shift durante l'intero ciclo di vita della pagina.
Ogni singolo layout shift viene calcolato moltiplicando due fattori:
- Impact Fraction: la percentuale del viewport occupata dall'area combinata dell'elemento prima e dopo lo spostamento
- Distance Fraction: la distanza percorsa dall'elemento rispetto al viewport
La formula è semplice, almeno sulla carta:
Layout Shift Score = Impact Fraction × Distance Fraction
Facciamo un esempio concreto per capirci meglio. Un elemento alto 200px in un viewport di 800px si sposta verso il basso di 100px:
- Impact Fraction = (200 + 100) / 800 = 0.375
- Distance Fraction = 100 / 800 = 0.125
- Layout Shift Score = 0.375 × 0.125 = 0.047
Sembra poco, vero? Ma questi valori si accumulano in fretta.
Google raggruppa gli shift in finestre di sessione (massimo 5 secondi, con gap massimo di 1 secondo tra uno shift e l'altro) e prende il punteggio della finestra con il valore più alto. Un dettaglio che vale la pena sottolineare: gli spostamenti che avvengono entro 500ms da un'interazione utente (click, tap, pressione di un tasto) vengono esclusi dal calcolo — Google sa che l'utente se li aspetta. Almeno su questo, l'algoritmo è ragionevole.
Le 5 Cause Principali del CLS e Come Risolverle
1. Immagini e Video Senza Dimensioni Esplicite
Questa è la causa numero uno di CLS nel web. Punto. Quando il browser incontra un tag <img> senza attributi width e height, alloca inizialmente zero spazio. Poi, quando l'immagine viene scaricata, tutto il contenuto sottostante viene spinto in basso — generando un layout shift enorme.
La cosa frustrante è che la fix è banale.
Soluzione base — attributi HTML:
<!-- ❌ SBAGLIATO: nessuna dimensione, CLS garantito -->
<img src="hero.jpg" alt="Hero image">
<!-- ✅ CORRETTO: il browser riserva lo spazio -->
<img src="hero.jpg" alt="Hero image" width="1200" height="675" loading="lazy">
Per le immagini responsive, aggiungi il CSS che ne preserva il rapporto d'aspetto:
img {
max-width: 100%;
height: auto;
}
I browser moderni calcolano automaticamente l'aspect-ratio dagli attributi width e height. Ma puoi anche dichiararlo esplicitamente per contenitori come i wrapper video:
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
background-color: #1a1a1a;
}
.thumbnail-grid img {
aspect-ratio: 4 / 3;
width: 100%;
object-fit: cover;
}
2. Web Font che Causano FOUT e FOIT
I web font sono un killer silenzioso del CLS. Onestamente, è uno dei problemi più insidiosi perché a occhio nudo sembra tutto a posto — finché non guardi i numeri.
Il problema si manifesta in due modi:
- FOUT (Flash of Unstyled Text): il testo appare con il font di fallback, poi viene ri-renderizzato con il font custom — causando uno shift se le metriche dei due font differiscono
- FOIT (Flash of Invisible Text): il testo è nascosto finché il font custom non viene scaricato, poi appare tutto insieme
Soluzione 1 — font-display: optional per CLS zero:
@font-face {
font-family: 'MioFont';
src: url('miofont.woff2') format('woff2');
font-display: optional;
}
Con optional, il browser usa il font custom solo se è già in cache o se si scarica entro ~100ms. Altrimenti usa il fallback senza mai fare lo swap. Risultato: zero CLS, garantito. Dalla seconda visita in poi il font custom sarà in cache e verrà usato normalmente.
È il mio approccio preferito, a meno che il brand non richieda assolutamente il font custom fin dalla prima visita.
Soluzione 2 — Font metric overrides per font-display: swap:
Se invece preferisci usare swap per mostrare sempre il font custom, puoi minimizzare il CLS creando un font di fallback con metriche identiche:
/* Fallback personalizzato che replica le metriche del font web */
@font-face {
font-family: 'Fallback-Per-Merriweather';
src: local('Georgia');
size-adjust: 106%;
ascent-override: 90.4%;
descent-override: 27.3%;
line-gap-override: 0%;
}
body {
font-family: 'Merriweather', 'Fallback-Per-Merriweather', serif;
}
I descrittori size-adjust, ascent-override, descent-override e line-gap-override allineano le dimensioni del font di fallback a quelle del font web, rendendo lo swap praticamente invisibile. Puoi calcolare questi valori con tool come Fontaine o il Font Fallback Generator di Google — ti risparmiano un sacco di tentativi manuali.
Soluzione 3 — Preload del font critico:
<link rel="preload" href="/fonts/miofont.woff2" as="font" type="font/woff2" crossorigin>
Il preload forza il browser a scaricare il font con priorità alta, riducendo la finestra di tempo in cui il fallback è visibile. Combinalo con una delle due soluzioni precedenti per risultati ancora migliori.
3. Annunci, Embed e iFrame Senza Dimensioni Riservate
Banner pubblicitari, video YouTube, post social embeddati, widget di terze parti — tutti questi elementi hanno un problema in comune: non dichiarano le proprie dimensioni in anticipo. Quando si caricano (spesso con tempi imprevedibili), spingono tutto il contenuto circostante.
Se gestisci un sito con annunci, probabilmente lo sai già fin troppo bene.
Soluzione — Placeholder con dimensioni fisse:
<!-- Slot pubblicitario con spazio riservato -->
<div class="ad-slot" style="min-height: 250px; min-width: 300px;">
<!-- L'annuncio si carica qui senza causare shift -->
</div>
<!-- iFrame video con aspect-ratio -->
<div class="video-embed">
<iframe src="https://www.youtube-nocookie.com/embed/ID_VIDEO"
width="560" height="315"
loading="lazy"
title="Titolo video">
</iframe>
</div>
.video-embed {
aspect-ratio: 16 / 9;
width: 100%;
max-width: 560px;
}
.video-embed iframe {
width: 100%;
height: 100%;
border: 0;
}
Se gli annunci hanno dimensioni variabili, usa la dimensione più grande attesa come min-height. È meglio avere un po' di spazio bianco che uno shift visibile — fidati su questo.
4. Contenuto Iniettato Dinamicamente Sopra il Fold
Cookie banner, notifiche, barre promozionali — ogni elemento che JavaScript inserisce nel DOM sopra il contenuto esistente causa un layout shift. Il punto chiave è "sopra": inserire contenuti sotto il viewport (dove l'utente non sta guardando) non genera CLS.
Sembra ovvio, eppure continuo a vedere siti che iniettano banner promozionali in cima alla pagina con un bel push di tutto il contenuto sottostante.
Soluzione — Overlay invece di inserimento nel flusso:
/* Cookie banner come overlay fisso, NON nel flusso del documento */
.cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
/* Non causa CLS perché non sposta nulla */
}
/* Se DEVI inserire un banner in alto, riserva lo spazio */
.promo-bar-wrapper {
min-height: 48px;
}
.promo-bar {
height: 48px;
background: #2563eb;
color: white;
}
Per le notifiche temporanee, usa position: fixed o position: sticky — entrambe le proprietà rimuovono l'elemento dal flusso del documento e non causano shift.
5. Animazioni che Usano Proprietà di Layout
Questa è più subdola. Animare proprietà come top, left, width, height, margin o padding causa layout shift, anche se l'elemento è in posizione assoluta. Queste proprietà forzano il browser a ricalcolare il layout dell'intera pagina ad ogni frame.
Soluzione — Usa transform e opacity:
/* ❌ SBAGLIATO: animare top/left causa layout shift */
.slide-in {
animation: slideWrong 0.3s ease;
}
@keyframes slideWrong {
from { top: -100px; }
to { top: 0; }
}
/* ✅ CORRETTO: transform opera sul compositor layer */
.slide-in {
animation: slideRight 0.3s ease;
}
@keyframes slideRight {
from { transform: translateY(-100px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
Le proprietà transform e opacity operano sul compositor layer del browser: non forzano ricalcoli di layout, non causano CLS e offrono performance a 60fps. È una di quelle regole che, una volta interiorizzata, ti fa risparmiare un mucchio di problemi.
Tecniche Avanzate: CSS Containment e content-visibility
Ok, adesso entriamo nel territorio delle ottimizzazioni più avanzate. Se hai già sistemato le cinque cause principali e il tuo CLS è ancora sopra 0.1, queste tecniche possono fare la differenza.
CSS Containment con la Proprietà contain
La proprietà CSS contain è uno strumento potentissimo e ancora sottoutilizzato. Dice al browser: "Niente all'interno di questo elemento può influenzare il layout esterno." In pratica, tronca la propagazione dei layout shift, isolandoli dentro il loro contenitore.
/* Isola gli slot pubblicitari dal resto della pagina */
.ad-container {
contain: layout size;
min-height: 250px;
}
/* Isola i widget di terze parti */
.third-party-widget {
contain: layout;
}
/* Isola sezioni indipendenti della pagina */
.sidebar, .comments-section, .related-posts {
contain: layout;
}
I valori principali di contain sono:
layout: l'elemento non influenza il layout degli elementi esterni, e viceversasize: le dimensioni dell'elemento non dipendono dai figli (devi specificare dimensioni esplicite)paint: niente all'interno viene renderizzato fuori dai confini dell'elementocontent: equivale alayout paintstrict: equivale alayout size paint— il più aggressivo
content-visibility per Contenuti Below-the-Fold
La proprietà content-visibility: auto è stata una vera rivelazione quando l'ho provata la prima volta. Dice al browser di non renderizzare i contenuti fuori dal viewport, risparmiando una quantità significativa di tempo di rendering. Ma senza precauzioni, può causare CLS quando l'utente scrolla e i contenuti appaiono con dimensioni diverse da quelle previste.
La soluzione? Combinarla con contain-intrinsic-size:
/* Sezioni below-the-fold: non renderizzate finché non servono */
.article-section {
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
/* Lista di prodotti: ogni card ha un'altezza intrinseca stimata */
.product-card {
content-visibility: auto;
contain-intrinsic-size: auto 350px;
}
/* Commenti in fondo alla pagina */
.comments-container {
content-visibility: auto;
contain-intrinsic-size: auto 800px;
}
La keyword auto in contain-intrinsic-size permette al browser di ricordare le dimensioni reali dopo il primo rendering, evitando shift nelle visite successive. I vantaggi sono duplici: le prestazioni di rendering migliorano drasticamente (fino al 50% di riduzione del rendering time su pagine lunghe) e il CLS rimane sotto controllo.
Un consiglio: non usare valori a caso per le altezze stimate. Carica la pagina, misura l'altezza reale delle sezioni con DevTools, e usa quei numeri. Più sono accurati, meno shift avrai.
Come Debuggare il CLS: Trovare gli Elementi Colpevoli
Chrome DevTools — Performance Panel
Il modo più efficace per identificare gli shift è il pannello Performance di Chrome DevTools. Ecco come fare:
- Apri DevTools (
F12oCmd+Opt+Isu Mac) - Vai al tab Performance
- Clicca su Record, ricarica la pagina, poi stoppa la registrazione
- Espandi la sezione Experience — vedrai le barre rosse dei layout shift
- Clicca su ogni shift per vedere quali elementi si sono spostati e di quanto
Il pannello mostra rettangoli viola sugli elementi spostati e, dal 2026, include un nuovo insight "Cumulative Layout Shift culprits" che identifica automaticamente le cause principali. Molto comodo.
Attenzione critica: DevTools mostra l'elemento che subisce lo shift, non necessariamente quello che lo causa. Se vedi un paragrafo di testo che si sposta, la causa potrebbe essere un'immagine sopra di esso che si carica senza dimensioni. Ho perso un bel po' di tempo la prima volta che ho debuggato un CLS proprio per questo — cercavo il problema nell'elemento sbagliato.
Monitoraggio in Produzione con la Libreria web-vitals
I dati di laboratorio (Lighthouse, DevTools) catturano solo gli shift al caricamento iniziale. Per il CLS reale — inclusi gli shift durante scroll e interazioni — serve il Real User Monitoring:
import { onCLS } from 'web-vitals';
onCLS((metric) => {
console.log('CLS:', metric.value);
console.log('Entries:', metric.entries);
// Invia i dati al tuo analytics
metric.entries.forEach((entry) => {
entry.sources?.forEach((source) => {
console.log('Elemento spostato:', source.node);
console.log('Spostamento precedente:', source.previousRect);
console.log('Spostamento attuale:', source.currentRect);
});
});
});
La libreria web-vitals cattura esattamente lo stesso valore che Google usa per il ranking, al 75° percentile dei tuoi utenti reali. Non quello di laboratorio, non un'approssimazione — il dato che conta davvero.
Checklist Operativa: Porta il CLS a Zero
Prima di chiudere, ecco una checklist rapida da seguire su ogni pagina. Salvala, stampala, appendila al monitor — come preferisci:
- Immagini: verifica che ogni
<img>e<video>abbia attributiwidtheheight - Font: usa
font-display: optionaloppure configura i font metric overrides consize-adjust - Annunci: assegna
min-heighta ogni slot pubblicitario - Embed: wrappa ogni iframe in un contenitore con
aspect-ratio - Contenuti dinamici: inseriscili sotto il viewport o usa
position: fixed - Animazioni: usa solo
transformeopacity, mai proprietà di layout - Terze parti: isola i widget con
contain: layout - Below-the-fold: applica
content-visibility: autoconcontain-intrinsic-size - Preload font: aggiungi
<link rel="preload">per i font critici above-the-fold - Monitora: implementa RUM con la libreria
web-vitalsper dati reali
Domande Frequenti sul Cumulative Layout Shift
Qual è un buon punteggio CLS?
Google considera buono un CLS di 0.1 o inferiore, da migliorare tra 0.1 e 0.25, e scarso oltre 0.25. La misurazione avviene al 75° percentile dei caricamenti reali, il che significa che il 75% delle visite alla tua pagina deve avere un CLS pari o inferiore a 0.1 per superare la soglia.
Il CLS influisce sul posizionamento SEO?
Sì, eccome. Il CLS è uno dei tre Core Web Vitals che Google utilizza come segnale di ranking nel suo algoritmo di page experience. I siti con CLS scarso possono posizionarsi più in basso rispetto ai competitor con layout stabili. Nel 2026, questi dati influenzano anche la visibilità nelle risposte AI generate da Gemini e negli AI Overviews — un motivo in più per prenderlo sul serio.
Perché il mio CLS è diverso tra Lighthouse e dati reali?
È una delle domande più frequenti che ricevo. Lighthouse misura il CLS solo durante il caricamento iniziale della pagina, in condizioni di laboratorio controllate. I dati reali (CrUX/field data) catturano anche gli shift che avvengono durante lo scroll, le interazioni utente e il caricamento lazy di contenuti. Il risultato? Spesso il CLS reale è sensibilmente più alto di quello di Lighthouse, specialmente su pagine con annunci o contenuti dinamici.
font-display: swap causa layout shift?
Può causarlo, sì. Quando il browser scambia il font di fallback con il font web, se le metriche dei due font sono diverse (altezza x, spaziatura, larghezza dei glifi), il testo cambia dimensione e tutto il contenuto circostante si sposta. Per evitarlo, usa font-display: optional per CLS zero, oppure configura i font metric overrides (size-adjust, ascent-override, descent-override) per allineare il font di fallback a quello web.
Come posso trovare gli elementi che causano CLS sulla mia pagina?
Usa il pannello Performance di Chrome DevTools: registra un caricamento di pagina, espandi la sezione Experience e clicca sui layout shift per vedere gli elementi coinvolti. Per i dati di produzione, implementa la libreria web-vitals con il callback onCLS, che espone le entries con proprietà sources contenenti il nodo DOM e le coordinate dello spostamento. Combinando dati di laboratorio e di campo, avrai il quadro completo.