Cumulative Layout Shift (CLS) mittaa sivun visuaalista vakautta — eli sitä, kuinka paljon sisältö pomppii yllättäen latauksen ja käytön aikana. Se on kolmesta Core Web Vitals -mittarista ehkä se vähiten ymmärretty, mutta sen vaikutus käyttökokemukseen on ihan valtava. Kuvittele tilanne: olet juuri klikkaamassa linkkiä, ja sisältö hyppää alaspäin. Painatkin mainosta linkin sijaan. Ärsyttävää, eikö? Juuri tällaisia tilanteita CLS pyrkii mittaamaan ja ehkäisemään.
Googlen asettama hyvä raja-arvo on alle 0,1, mitattuna 75. prosenttipisteessä. Arvot 0,1–0,25 vaativat parannusta, ja yli 0,25 on jo suoraan huono. Vuoden 2026 CrUX-datan mukaan 81 % mobiilisivuista läpäisee CLS:n hyvän raja-arvon, mikä tekee siitä parhaiten suoriutuvan Core Web Vital -mittarin.
Mutta älä anna sen tuudittaa väärään turvallisuudentunteeseen. Jos sivustosi kuuluu jäljelle jäävään 19 prosenttiin, vaikutus käyttökokemukseen ja hakusijoituksiin on hyvin konkreettinen.
Tämä opas on kolmas osa Core Web Vitals -sarjaamme. Olemme aiemmin käsitelleet INP-optimointia ja LCP-optimointia, ja nyt on CLS:n vuoro. Käydään läpi, mistä siirtymät johtuvat ja miten ne korjataan — koodiesimerkein tietysti.
Miten CLS oikeastaan lasketaan?
CLS-arvo ei ole yksinkertainen millisekuntiluokitus kuten LCP tai INP. Se perustuu kahden tekijän tuloon: vaikutusosuus (impact fraction) ja etäisyysosuus (distance fraction). Kuulostaa monimutkaiselta, mutta avataan se esimerkin kautta.
- Vaikutusosuus kertoo, kuinka suuri osa näkymäalueesta (viewport) on elementin liikkumisen vaikutuspiirissä. Jos 400 pikseliä korkea elementti siirtyy 200 pikseliä alaspäin 800 pikselin näkymäalueessa, vaikutusalue kattaa 600 pikseliä (alkuperäinen + uusi sijainti), joten vaikutusosuus on 600 / 800 = 0,75.
- Etäisyysosuus taas kertoo, kuinka pitkän matkan elementti siirtyi suhteessa näkymäalueeseen. Samassa esimerkissä 200 / 800 = 0,25.
CLS-pistemäärä on näiden tulo: 0,75 × 0,25 = 0,1875. Tämä yksittäinen siirtymä ylittäisi jo "hyvä"-kynnyksen. Eli käytännössä pienetkin siirtymät voivat kumuloitua yllättävän isoksi CLS-arvoksi.
Tärkeä yksityiskohta: CLS mittaa vain odottamattomia siirtymiä. Jos käyttäjä klikkaa nappia ja sisältö laajenee vastauksena, sitä ei lasketa CLS:ään. Selain tunnistaa käyttäjän aloittamat siirtymät 500 ms:n sisällä syötteestä, mikä on hyvä tietää.
Kuusi yleisintä CLS-ongelman aiheuttajaa
1. Kuvat ja videot ilman mittoja
Tämä on edelleen yleisin yksittäinen CLS-ongelma, ja rehellisesti sanottuna se on myös helpoin korjata. Kun selain kohtaa kuvan ilman width- ja height-attribuutteja, se varaa sille aluksi nolla pikseliä tilaa. Kun kuva latautuu ja selain saa selville sen todellisen koon, kaikki alla oleva sisältö työntyy alaspäin. Klassinen layout shift.
<!-- VÄÄRIN: ei mittoja, aiheuttaa layout shiftin -->
<img src="tuote.jpg" alt="Tuotekuva">
<!-- OIKEIN: mitat määritelty, selain varaa tilan etukäteen -->
<img src="tuote.jpg" alt="Tuotekuva" width="800" height="600">
<!-- MYÖS OIKEIN: CSS aspect-ratio -->
<style>
.responsive-img {
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
}
</style>
<img src="tuote.jpg" alt="Tuotekuva" class="responsive-img">
Moderni lähestymistapa on käyttää CSS:n aspect-ratio-ominaisuutta yhdessä width- ja height-attribuuttien kanssa. Käytännössä nykyselaimet laskevat kuvasuhteen automaattisesti HTML-attribuuteista, joten pelkät width ja height riittävät useimmissa tapauksissa. Ei sen kummempaa.
2. Verkkofontit ja fonttien vaihto
Fontteihin liittyvä CLS on petollinen juttu, koska se ei näy laboratoriotyökaluissa yhtä selvästi kuin kenttädatassa. Olen nähnyt sivustoja, joilla Lighthouse näyttää puhdasta nollaa, mutta CrUX-data kertoo aivan toista tarinaa. Ongelma syntyy kahdella tavalla:
- FOUT (Flash of Unstyled Text): Selain näyttää ensin varafontin ja vaihtaa sitten verkkofonttiin. Jos fonttien mittasuhteet eroavat, teksti vie eri verran tilaa ja sisältö siirtyy.
- FOIT (Flash of Invisible Text): Teksti on näkymätön, kunnes verkkofontti latautuu. Kun teksti ilmestyy, se voi työntää muuta sisältöä.
Syvällisempi ratkaisu tähän on myöhemmin tässä oppaassa — kannattaa lukea eteenpäin.
3. Mainokset ja dynaamisesti lisätty sisältö
Mainospaikat ovat ehkä se turhauttavin CLS-lähde, koska niiden hallinta ei ole aina täysin omissa käsissäsi. Tyypillinen skenaario: sivu latautuu, käyttäjä alkaa lukea, ja kaksi sekuntia myöhemmin mainosbanneri ilmestyy artikkelin yläpuolelle ja työntää kaiken sisällön alaspäin. Tuttua?
<!-- VÄÄRIN: mainospaikka ilman varattua tilaa -->
<div id="ad-slot"></div>
<!-- OIKEIN: tila varattu min-height:llä -->
<div id="ad-slot" style="min-height: 250px;"></div>
<!-- VIELÄ PAREMPI: CSS containment ja varattu tila -->
<style>
.ad-container {
min-height: 250px;
contain: layout style paint;
content-visibility: auto;
contain-intrinsic-size: auto 300px 250px;
}
</style>
<div class="ad-container">
<div id="ad-slot"></div>
</div>
Ja tässä yksi tärkeä neuvo: älä koskaan poista varattua tilaa, vaikka mainos ei latautuisi. Se aiheuttaa yhtä suuren siirtymän kuin mainoksen ilmestyminen ilman tilaa — eli pahimmillaan tuplaosuma CLS:ään.
4. Evästebanneri ja ilmoituspalkit
Evästebanneri, joka työntää sivun sisältöä alaspäin, on yllättävän yleinen CLS-lähde (ja silti niin helppo korjata). Ratkaisu on käyttää position: fixed tai position: sticky -asettelua, jolloin banneri leijuu sisällön päällä eikä työnnä sitä. Näin yksinkertaista se on.
5. Laiska lataus näkymäalueen lähellä
Laiskasti ladatut kuvat juuri näkymäalueen alapuolella voivat aiheuttaa CLS:ää, kun käyttäjä vierittää ja kuva laajenee paikkavaraajasta täyteen kokoonsa. Ratkaisu on tuttu: määritä aina width ja height myös laiskasti ladatuille kuville. Selain laskee kuvasuhteen ja varaa tilan etukäteen.
6. A/B-testausskriptit
A/B-testaustyökalut, jotka muokkaavat DOM:ia alkuperäisen renderöinnin jälkeen, voivat aiheuttaa näkyviä siirtymiä. Tämä on sellainen asia, joka unohtuu helposti optimoinnissa. Suosi palvelinpuolen A/B-testausta tai tee muutokset suoraan alkuperäisessä HTML:ssä aina kun mahdollista.
Fonttien CLS-optimointi: size-adjust ja ascent-override
Tämä on mielestäni yksi vuoden 2026 tehokkaimmista CLS-optimointitekniikoista, ja silti yllättävän moni sivusto jättää sen kokonaan hyödyntämättä. Idea on pohjimmiltaan yksinkertainen: sen sijaan, että hyväksyt varafontin ja verkkofontin mittaerojen aiheuttaman siirtymän, säädät varafontin mittasuhteet vastaamaan verkkofonttia.
CSS:n @font-face-kuvaajat size-adjust, ascent-override, descent-override ja line-gap-override tekevät tämän mahdolliseksi:
/* Verkkofontin määrittely */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
}
/* Varafontin mittasuhteiden säätö vastaamaan Inter-fonttia */
@font-face {
font-family: 'Inter-fallback';
src: local('Arial');
size-adjust: 107.40%;
ascent-override: 90.20%;
descent-override: 22.48%;
line-gap-override: 0.00%;
}
body {
font-family: 'Inter', 'Inter-fallback', sans-serif;
}
Tällä tekniikalla fontin vaihdon aiheuttama CLS putoaa tyypillisesti 0,05–0,15:stä alle 0,01:een. Käytännössä nolla.
Mistä löydät oikeat arvot? Näppärimmin Fontaine-kirjastolla tai Next.js:n next/font-moduulilla, jotka laskevat arvot automaattisesti puolestasi. Voit myös käyttää Googlen Font Fallbacks -työkalua Chrome DevToolsissa — se on yllättävän kätevä.
Jos fonttien CLS on erityisen kriittinen eikä verkkofontin näkyminen hitailla yhteyksillä ole välttämätöntä, harkitse font-display: optional -arvoa. Se poistaa fontin vaihdon kokonaan — jos fontti ei ehdi latautua ensimmäiseen piirtoon, selain käyttää varafonttia koko sivuvierailun ajan. Nolla CLS:ää, taatusti.
CSS containment — alikäytetty suorituskykytyökalu
CSS:n contain-ominaisuus on yksi niistä tekniikoista, jotka ansaitsisivat paljon enemmän huomiota kuin mitä ne saavat. Se kertoo selaimelle, että elementin sisäiset muutokset eivät vaikuta elementin ulkopuoliseen asetteluun. Kuulostaa pieneltä asialta, mutta käytännössä sillä on valtava merkitys.
Neljä containment-tyyppiä:
- layout: Elementin sisäiset asettelumuutokset eivät vaikuta ulkopuolisiin elementteihin. Tämä on eniten hyötyä tuottava containment-tyyppi ja se, mistä kannattaa aloittaa.
- paint: Elementin lapset eivät piirry sen rajojen ulkopuolelle. Jos elementti on näkymäalueen ulkopuolella, sen lapsia ei tarvitse piirtää lainkaan.
- size: Elementin koko lasketaan ikään kuin sillä ei olisi lapsia. Estää sisällön koon muutosten vaikutuksen vanhemman kokoon.
- style: CSS-laskurit ja
content-ominaisuuden lainausarvot eristetään elementtiin.
/* Korttinäkymä: yksittäisen kortin muutokset eivät vaikuta muihin */
.product-card {
contain: layout style paint;
}
/* Artikkelilistaus: jokainen artikkeli on eristetty */
article {
contain: content; /* sama kuin layout paint style */
}
/* Mainospaikka: täydellinen eristys */
.ad-slot {
contain: strict; /* sama kuin size layout paint style */
min-height: 250px;
}
/* Pitkä lista: content-visibility piilottaa ei-näkyvät elementit */
.feed-item {
content-visibility: auto;
contain-intrinsic-size: auto 200px;
}
content-visibility: auto ansaitsee erityismaininnan. Se yhdistää containment-tekniikat ja kertoo selaimelle, että näkymäalueen ulkopuolella olevien elementtien renderöinti voidaan lykätä myöhemmäksi. Pitkillä sivuilla tämä voi parantaa alkuperäistä renderöintiaikaa jopa 50–70 %. Ei paha yhdelle CSS-riville.
Bfcache — unohdettu CLS-ase
Back/forward cache (bfcache) on selaimen mekanismi, joka tallentaa sivun täydellisen tilan muistiin, kun käyttäjä navigoi pois. Kun käyttäjä palaa takaisin- tai eteenpäin-painikkeella, sivu palautetaan muistista välittömästi — ilman yhtäkään layout shiftiä. Aika hienoa, vai mitä?
Tämä on erityisen merkittävää siksi, että CLS mitataan koko sivuvierailun ajan. Jos käyttäjä navigoi pois ja takaisin, ja sivu latautuu kokonaan uudelleen layout shifteineen, nuo siirtymät lasketaan uudelleen. Bfcache poistaa tämän ongelman kokonaan.
Miten varmistat bfcache-kelpoisuuden?
- Älä käytä
unload-tapahtumankuuntelijaa — se estää bfcachen lähes kaikissa selaimissa. Tämä on yleisin virhe. - Käytä
pagehide-tapahtumaaunload:in sijaan. - Vältä
Cache-Control: no-store-otsikkoa sivuilla, jotka eivät sisällä arkaluonteista dataa. - Sulje avoimet WebSocket- ja IndexedDB-yhteydet
pagehide-tapahtumassa.
// VÄÄRIN: unload estää bfcachen
window.addEventListener('unload', () => {
sendAnalytics();
});
// OIKEIN: pagehide sallii bfcachen
window.addEventListener('pagehide', (event) => {
if (!event.persisted) {
// Sivu ei tallennu bfcacheen — tee siivoustoimet
sendAnalytics();
}
});
// Bfcache-palautuksen tunnistaminen
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// Sivu palautettu bfcachesta — päivitä tarvittaessa
console.log('Sivu palautettu bfcachesta');
refreshTimestamps();
}
});
Voit testata sivusi bfcache-kelpoisuuden Chrome DevToolsin Application-välilehdellä kohdasta "Back/forward cache". Se kertoo suoraan, mikä bfcachen estää, jos jokin estää.
CLS-ongelmien diagnosointi PerformanceObserver-rajapinnalla
CLS:n korjaaminen edellyttää, että tiedät tarkalleen mitkä elementit siirtyvät. Arvailemalla ei pääse pitkälle. PerformanceObserver-rajapinta mahdollistaa layout shiftien reaaliaikaisen seurannan:
// Layout shiftien seuranta PerformanceObserverilla
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Ohita käyttäjän aloittamat siirtymät
if (!entry.hadRecentInput) {
console.group(`Layout shift: ${entry.value.toFixed(4)}`);
console.log(`Aika: ${entry.startTime.toFixed(0)} ms`);
// Näytä siirtyneet elementit
entry.sources?.forEach(source => {
console.log(`Elementti: ${source.node?.nodeName}`);
console.log(`Edellinen sijainti:`, source.previousRect);
console.log(`Nykyinen sijainti:`, source.currentRect);
});
console.groupEnd();
}
}
});
observer.observe({ type: 'layout-shift', buffered: true });
Tämä koodi kertoo tarkalleen, mikä elementti siirtyi, milloin ja kuinka paljon. hadRecentInput-tarkistus suodattaa pois käyttäjän aloittamat siirtymät, jotka eivät muutenkaan vaikuta CLS-arvoon.
Voit myös käyttää web-vitals-kirjastoa, jos haluat yksinkertaisemman lähestymistavan:
import { onCLS } from 'web-vitals';
onCLS((metric) => {
console.log('CLS:', metric.value);
// Lähetä analytiikkaan
navigator.sendBeacon('/analytics', JSON.stringify({
name: 'CLS',
value: metric.value,
id: metric.id,
entries: metric.entries.map(e => ({
value: e.value,
startTime: e.startTime,
sources: e.sources?.map(s => s.node?.nodeName)
}))
}));
}, { reportAllChanges: true });
CLS-optimoinnin tarkistuslista
Tässä vielä tiivistetty tarkistuslista, jota voit käyttää omalla sivustollasi. Tulosta vaikka itsellesi:
- Lisää
widthjaheightkaikkiin kuviin, videoihin ja iframe-elementteihin. - Käytä
aspect-ratio-ominaisuutta responsiivisissa mediaelementeissä. - Määritä varafontin mittasuhteet
size-adjust- jaascent-override-kuvaajilla. - Varaa tila mainospaikoille
min-height-ominaisuudella. - Käytä
position: fixedevästebannerissa sisällön työntämisen sijaan. - Lisää
contain: contenttoistuville sisältöelementeille (kortit, artikkelit). - Käytä
content-visibility: autopitkillä sivuilla. - Varmista bfcache-kelpoisuus: korvaa
unloadpagehide-tapahtumalla. - Esilataa kriittiset verkkofontit
<link rel="preload">-tunnisteella. - Seuraa kenttädataa CrUX:lla tai web-vitals-kirjastolla — laboratoriotyökalut eivät kerro koko totuutta.
Usein kysytyt kysymykset
Mikä on hyvä CLS-arvo ja miten se vaikuttaa hakusijoituksiin?
Googlen mukaan hyvä CLS-arvo on alle 0,1, mitattuna 75. prosenttipisteessä. CLS on yksi kolmesta Core Web Vitals -mittarista, jotka vaikuttavat Googlen hakualgoritmin sivukokemussignaaleihin. Sivustot, jotka läpäisevät kaikki kolme Core Web Vitals -kynnystä, näkevät keskimäärin 24 % alhaisemman välittömän poistumisprosentin ja paremman orgaanisen sijoituksen. Eli kyllä, tällä on väliä.
Miksi CLS-arvoni on erilainen DevToolsissa kuin Google Search Consolessa?
Tämä on yksi yleisimmistä kysymyksistä, ja vastaus on yksinkertainen. DevTools mittaa CLS:ää vain laboratorioympäristössä sivun latauksen aikana. Search Console käyttää kenttädataa (CrUX), joka mittaa todellisten käyttäjien kokemuksia koko sivuvierailun ajan — mukaan lukien vierittämisen jälkeen tapahtuvat siirtymät, mainokset ja dynaaminen sisältö. Kenttädata on aina luotettavampi mittari todellisesta käyttökokemuksesta. Lisäksi Search Console päivittää datan 28 päivän liukuvan keskiarvon perusteella, joten muutokset näkyvät viiveellä.
Kuinka paljon font-display: swap vaikuttaa CLS-arvoon?
font-display: swap voi aiheuttaa merkittävän CLS-arvon (0,05–0,15), jos varafontin ja verkkofontin mittasuhteet eroavat paljon. Paras ratkaisu on yhdistää swap varafontin mittasuhteiden säätöön (size-adjust, ascent-override), jolloin CLS putoaa alle 0,01:een. Jos haluat nolla CLS:ää takuulla, käytä font-display: optional — mutta tiedosta, että hitailla yhteyksillä verkkofontti ei ehkä näy lainkaan.
Miten CSS contain-ominaisuus auttaa CLS-optimoinnissa?
CSS:n contain-ominaisuus ei suoraan poista layout shiftejä, mutta se estää niiden leviämisen muualle sivulle. Kun asetat contain: layout elementille, sen sisällä tapahtuvat asettelumuutokset eivät vaikuta vanhemman tai sisarelementtien asetteluun. Tämä on erityisen hyödyllistä mainospaikoille ja dynaamiselle sisällölle, koska yksittäisen elementin sisällä tapahtuva siirtymä ei kumuloidu koko sivun CLS-arvoon.
Vaikuttaako laiska lataus CLS-arvoon?
Kyllä, ehdottomasti — jos laiskasti ladatut elementit ovat lähellä näkymäalueen yläreunaa. Kun käyttäjä vierittää ja kuva latautuu ilman varattua tilaa, se työntää alla olevaa sisältöä alaspäin. Ratkaisu on onneksi yksinkertainen: määritä aina width- ja height-attribuutit myös laiskasti ladatuille kuville, jolloin selain varaa tilan etukäteen kuvasuhteen perusteella.