Los Layout shift culprits op in Lighthouse en PageSpeed

Wat er bewoog tijdens het laden van je pagina, hoe je het element controleert dat Lighthouse aanwijst, en hoe je shifts door afbeeldingen, fonts, ads, balken en late CSS oplost.

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-11-05

Waarom kleurt Layout shift culprits rood in PageSpeed Insights?

Layout shift culprits kleurt rood als de layout shifts tijdens het laden optellen tot een CLS van 0,1 of hoger: "Layout shifts ontstaan als elementen bewegen zonder interactie van de gebruiker." Vroeger noemde Lighthouse dit Avoid large layout shifts. Het toont wat er verschoof en doet een gok naar de oorzaak. Reserveer ruimte voor wat laat binnenkomt: een width en height op afbeeldingen, een hoogte voor ad slots.

Direct de snelle fix? Spring naar afbeeldingen, web fonts, ads en embeds of banners.

Hoe lees je de tabel Layout shift culprits?

Lighthouse registreert één laadsessie van je pagina op telefoonformaat en toont de layout shifts in bursts (verschuivingen binnen een seconde van elkaar), met één tabel per burst. Scoort de slechtste burst 0,1 of hoger, dan kleurt de melding rood; daaronder blijft hij grijs. Streef naar 0, ook als hij grijs is: een verschuiving onder de 0,1 verplaatst nog steeds de tekst die je bezoeker leest.

Op een receptpagina die ik bouwde als een WordPress-foodblog, met een web font voor de kop, een nieuwsbriefplugin, een ad onder de header en een receptenplugin, kleurde de melding rood met één burst van 0,241.

Lighthouse-melding Layout shift culprits in het rood op een receptpagina: Total 0,241 en vier shifts. De verschoven elementen zijn div.entry-content, main, de titel van de receptkaart en article. Onder drie ervan is de oorzaakregel Unsized image element, wijzend naar de featured image met width 1200 en height 675; de fontregel toont ook Web font en de receptkaartregel noemt de receptfoto.

De melding van de receptpagina in Lighthouse 13.5, mobiel, gesimuleerde throttling zoals in PageSpeed Insights.

  • Total is de score van de burst; het Total van de slechtste burst is de CLS die Lighthouse rapporteert.
  • Element is wat in die shift het meest bewoog, vaak een grote container zoals <main>. Wat het element wegdrukte, staat er meestal boven.
  • Layout shift score is het aandeel van die ene shift in het Total.
  • De regel onder een rij is de gok van Lighthouse naar de oorzaak: "Unsized image element" bij de afbeelding, of "Web font" bij het fontbestand. Een rij kan beide hebben, of geen van tweeën.

Op de receptpagina noemde Lighthouse bij drie van de vier shifts de verkeerde afbeelding: controleer welk element echt bewoog voordat je iets aanpast.

Waarom springt de pagina tijdens het laden?

Je bezoeker begint te lezen en de tekst schuift weg, of tikt op een link en raakt wat er net voor in de plaats is geschoven.

De browser rendert de pagina met wat er is. Alles wat later binnenkomt zonder dat er ruimte voor is gereserveerd, drukt de content eronder weg: een afbeelding zonder afmetingen begint op nul hoogte, een web font zet de kop breder neer dan de fallback deed. Elke verschuiving is een layout shift, berekend op basis van hoeveel van het scherm bewoog en hoe ver. Shifts die minder dan een seconde na elkaar vallen vormen samen één burst, en CLS is de score van de slechtste burst. Kleine verschuivingen vlak na elkaar tellen dus snel op.

Op de receptpagina blijft elke oorzaak op zichzelf onder de 0,1, en alle vier vallen ze in één burst:

Oorzaak op de receptpaginaCLS op zichzelfNa de fix
Web font van de kop0,0350
Receptfoto zonder width en height0,0360
Nieuwsbriefbalk boven de header0,0580
Ad in een leeg slot0,0540
Alle vier samen0,2410

Samen scoren ze hoger dan de som van de rijen, omdat het font en de foto op de volledige pagina elk een groter deel van het scherm verplaatsen. Door alle vier aan te pakken ging de mobiele Performance-score van 87 naar 100, terwijl FCP, LCP en Total Blocking Time gelijk bleven. CLS telt mee voor een kwart van die score, maar Google rankt je pagina op de CLS die je echte bezoekers krijgen.

De receptpagina op een telefoon bij de first paint: header, een leeg ad-label, de kop over twee regels in het fallback-font, de auteursregel en de featured foto.

Voor, first paint

Dezelfde pagina twee seconden later: een groene nieuwsbriefbalk boven de header, een ad eronder, de kop over drie regels in het web font en de foto lager dan het stippelkader dat aangeeft waar hij bij de first paint stond.

Voor, op 2 seconden

De herstelde pagina bij de first paint: een lege strook waar de balk komt, een leeg ad slot op volledige hoogte en de kop al over drie regels in het aangepaste fallback-font.

Na, first paint

De herstelde pagina twee seconden later: de balk en de ad hebben hun ruimte gevuld, de kop staat in het web font en de foto staat weer op de plek van de first paint.

Na, op 2 seconden

De receptpagina op het telefoonformaat van Lighthouse. Het stippelkader geeft aan waar de foto stond bij de first paint.

Wees Lighthouse het juiste element aan?

Lighthouse gokt de oorzaak op basis van timing: een web font dat net voor de shift klaar was met downloaden, of een afbeelding die de browser er direct na renderde. Daardoor kan een afbeelding die alleen werd weggedrukt toch als "Unsized image element" in de lijst belanden. De featured image van de receptpagina heeft width="1200" height="675", maar toch noemt Lighthouse hem bij de shifts van het font, de nieuwsbriefbalk en de ad. Heeft een rij geen regel eronder, begin dan bij stap 2.

  1. Heeft de genoemde afbeelding afmetingen? "Image elements do not have explicit width and height", onder Diagnostics in hetzelfde rapport, toont afbeeldingen die dit missen. In DevTools toont het tabblad Computed bij de afbeelding een ratio voor aspect-ratio (auto 1200 / 675) als er ruimte is gereserveerd, en alleen auto als dat niet zo is. Een afbeelding met afmetingen veroorzaakte de shift niet, tenzij het thema width: auto forceert (zie Unsized image element hieronder): kijk naar de andere regel onder de rij, vaak "Web font", of ga door naar stap 2.
  2. Wat verscheen er op dat moment? Neem een reload op in het Performance-paneel. Elke shift is een paarse ruit in de Layout shifts-track; hover erover voor de score en een screenshot van dat moment waarop het verschoven vlak is gemarkeerd. Op de receptpagina toont de shift van 0,0583 de groene nieuwsbriefbalk die net bovenin verscheen. Voor shifts tijdens het scrollen klik je op Record, scrol je en klik je op Stop.
  3. Blokkeer de verdachte en reload. Klik in het Network-paneel met rechts op het verdachte request, zoals het fontbestand of de ad-tag, kies Block requests en daarna Block request URL, en neem de pagina opnieuw op. Een ruit die weg is, hoorde bij dat request: het blokkeren van het font op de receptpagina nam één shift weg en liet de andere drie staan. Vink de blokkade achteraf weer uit in de drawer Request conditions.
Chrome DevTools Performance-paneel: de Layout shifts-track met vier ruiten in het slechtste layout shift-cluster, de tooltip van de shift van 0,0583 toont de pagina met een groene nieuwsbriefbalk die net boven de header is verschenen, en links toont de melding Layout shift culprits twee afbeeldingen zonder afmetingen en een web font.

Stap 2 op de receptpagina: Chrome 155, Slow 4G en 4× CPU-vertraging.

Zonder DevTools doet cRUM in CoreDash deze check: het legt een Chrome-trace van de pagina naast wat je echte bezoekers te zien kregen, noemt het verschoven element en laat zien wat de bezoeker vlak daarvoor deed. Het screenshot hieronder is van een andere pagina: begin bij Chrome trace en Real users.

CoreDash cRUM voor één pagina op mobiel: Chrome-trace bovenaan, en onder Real users de panelen When it shifts, What shifts en What preceded it.

CoreDash cRUM voor één pagina op acme-pagespeed.com, mobiel.

Welke elementen verschuiven bij je echte bezoekers?

Lighthouse laadt je pagina één keer, als nieuwe bezoeker, en scrolt of tikt nooit. Je field data bevat elk echt bezoek, inclusief de ad midden in een post die pas laadt zodra de bezoeker ernaartoe scrolt en de header die krimpt tijdens het scrollen. Is Lighthouse schoon of grijs terwijl Search Console meldt dat de CLS onvoldoende is, dan vinden de verschuivingen meestal pas na het laden plaats.

Zet in CoreDash het device op mobiel en groepeer CLS op Attribution Element (CLS) om te zien welke elementen bewogen; Last interaction (CLS), onder More dimensions, laat zien of dat na een scroll gebeurde. Om te achterhalen wat die elementen wegdrukte, neem je die pagina op in het Performance-paneel terwijl je scrolt (stap 2 van de check hierboven).

CoreDash, Cumulative Layout Shift per Attribution Element (CLS) op mobiel: per element de p75-CLS, een performancelabel, de verdeling en de sessies; de rijen bevatten main#main.site-main, de receptnaam, een in-content ad slot, de navigatie en de footer.

CoreDash op mobiel, p75: begin bij de hoogste CLS.

Hoe los je Layout shift culprits op?

Begin bij de grootste shift in de tabel met het hoogste Total, zodra de check hierboven de oorzaak heeft bevestigd.

Unsized image element

Een afbeelding zonder afmetingen begint op nul hoogte en drukt bij het laden alles eronder naar beneden. WordPress voegt automatisch een width en height toe aan afbeeldingen uit de mediabibliotheek; afbeeldingen die een thema, pagebuilder, slider of plugin zelf injecteert ontbreken die vaak, zoals de foto van de receptkaart. Kun je het thema niet aanpassen, dan voegt WP Rocket de ontbrekende afmetingen toe (Media, Add missing image dimensions); in een template schrijf je ze zelf:

<img src="shakshuka.jpg" width="800" height="600" alt="Shakshuka in the pan">
img { max-width: 100%; height: auto; }

De browser zet de twee attributen om in een verhouding en reserveert bij elke schermbreedte de juiste hoogte nog voor het bestand binnenkomt. Een width: auto in de CSS van het thema heft die verhouding op (auto sizing images). Op Shopify voegt het image_tag-filter de afmetingen toe; schrijft een thema of app zelf een <img>, dan is width="{{ image.width }}" height="{{ image.height }}" nodig. In Next.js vereist next/image een width en height, of fill binnen een parent met een eigen afmeting.

Web font

De kop verschijnt eerst in het fallback-font en daarna pas in het web font, dat breder of smaller is: regels breken op andere woorden af en alles eronder schuift op. In WordPress (Extra CSS) en Shopify (de stylesheet van het thema onder Code bewerken; een thema-update kan dit overschrijven) voeg je een fallback-fontface toe die een lokaal font schaalt naar de afmetingen van het web font. In Next.js zorgt next/font voor een passende fallback. Het font van de kop op de receptpagina:

@font-face {
  font-family: "Young Serif Fallback";
  src: local("Times New Roman"), local("Liberation Serif");
  size-adjust: 131%;
  ascent-override: 79.85%;
  descent-override: 27.94%;
  line-gap-override: 0%;
}
h1 { font-family: "Young Serif", "Young Serif Fallback", serif; }

Een size-adjust van 122% uit een calculator liet daar nog een CLS van 0,029 achter; 131% bracht die naar 0. Font display toont de Android-face, hoe je aan je eigen getallen komt en behandelt font-display: optional, wat de shift voorkomt en nieuwe bezoekers direct de fallback toont.

Ads, video's en andere embeds

Een ad-script plaatst zijn iframe pas na de veiling in het slot; was het slot leeg, dan zakt alles eronder met de hoogte van de advertentie. Volgens de State of Web Vitals slaagt 89% van de sites zonder advertentienetwerk voor CLS op telefoons, tegenover 73% van de sites met twee of meer netwerken. Dat is een correlatie.

Geef de container van de advertentie in het thema of de ad-plugin de hoogte van het formaat dat het vaakst verschijnt, en maak van de iframe een block:

.ad-slot { min-height: 50px; }
.ad-slot iframe { display: block; }

Een iframe staat standaard op de tekstregel, waardoor het gereserveerde slot op de receptpagina alsnog 8 pixels groeide totdat de iframe een block werd. Laat het slot open als er geen advertentie is: inklappen verschuift de pagina net zo hard als vullen. AdSense Auto ads verschijnen waar Google dat bepaalt: sluit de zones op het eerste scherm uit (Advertentie-instellingen, Uitgesloten gebieden) en plaats daar een handmatige ad unit in een slot met vaste afmetingen.

YouTube, kaarten en andere embeds zijn ook iframes, en bij een iframe leveren de width- en height-attributen geen aspect-ratio op: met height: auto krimpt hij naar zo'n 150 pixels. Geef de iframe een aspect-ratio, of gebruik een facade: een thumbnail met afmetingen die bij een klik verandert in de iframe (YouTube, Google Maps):

.video-embed iframe { width: 100%; height: auto; aspect-ratio: 16 / 9; }

Balken en banners die een script toevoegt

Een nieuwsbriefbalk of gratis-verzending-strook die een script boven de header plaatst, drukt de hele pagina omlaag.

Begin bij de layout-instelling van de plugin of app: een balk onderaan, een zwevend blok of een pop-up ligt over de pagina en verschuift niets. Consent-tools zoals OneTrust en CookieYes bieden dezelfde layouts; Lighthouse ziet hun banner bij elke test omdat het zonder cookies start. Moet een balk per se boven de header staan, reserveer de ruimte dan in het thema op de hoogte die het tabblad Computed toont op telefoonbreedte:

<div id="promo-slot" style="min-height: 48px"></div>

Voor een balk die maar bij een deel van de bezoeken verschijnt: laat hem zweven, of render hem op de server in de HTML. Zichtbare content uit een Google Tag Manager-tag arriveert altijd na de first paint: verplaats die naar de template. Op Shopify heeft een app die een widget injecteert, zoals reviewsterren of een aankondigingsbalk, een hoogte nodig op de container waar het app block in landt.

CSS die laadt na de first paint

Een stylesheet die laadt via het media="print"-trucje, of die door een optimalisatieplugin wordt uitgesteld of asynchroon wordt geladen, komt pas binnen als de pagina al op het scherm staat en past de afmetingen aan van wat er al staat: groeit de header, dan drukt die alles eronder weg. Sluit de stylesheet in de plugin uit van de optie die CSS vertraagt of asynchroon laadt; laad hem in het thema gewoon met een standaard <link rel="stylesheet"> in de head.

Animaties, sliders en blokken gerenderd door JavaScript

Deze komen meestal uit het thema, een plugin of een app: zoek daar naar een instelling. Is die er niet, stuur een developer dan het element uit het rapport en de onderstaande code.

Een animatie op top, left, height of margin verplaatst de layout in elk frame. Lighthouse toont dit als meerdere kleine rijen zonder oorzaakregel, en "Avoid non-composited animations" onder Diagnostics noemt het element. Animeer in plaats daarvan met transform: dat verplaatst de pixels van het element zelf en niets eromheen, waardoor de browser geen layout shift telt.

.promo { position: fixed; top: 0; left: 0; right: 0;
         transform: translateY(-100%); transition: transform .3s; }
.promo.is-open { transform: translateY(0); }

Een slider toont zijn slides gestapeld totdat het script draait; houd hem vast op de eerste slide tot het script zijn ready-class toevoegt (gebruik de class van je eigen slider):

.slider:not(.is-ready) > :not(:first-child) { display: none; }

Een blok dat JavaScript pas na het laden rendert, zoals reviews of gerelateerde producten, vereist een lege container op de uiteindelijke hoogte, of rendering op de server. Voor animaties bij het scrollen, zie scroll-triggered animations en CSS transitions.

Shifts uit code die je niet kunt aanpassen

Wat een ad creative, een app zonder layout-instelling of het script van een leverancier invoegt, kun je niet aanpassen. Reserveer de hoogte die het meestal inneemt, plaats het onder het eerste scherm of vervang het door een variant die netjes binnen zijn eigen ruimte blijft.

Brachten de aanpassingen de CLS naar 0?

Controleer de fix in Lighthouse op mobiel, nadat je de cache van je caching-plugin en CDN hebt geleegd. Zijn alle shifts verdwenen, dan staat Layout shift culprits bij de geslaagde audits en is de CLS 0. Een opname in het Performance-paneel, eenmaal met een reload en eenmaal tijdens het scrollen, toont een lege Layout shifts-track. CoreDash toont de nieuwe CLS per element vanaf de eerstvolgende bezoeken; Search Console doet er tot 28 dagen over.

Lighthouse op telefoonbreedte op de receptpagina voor de fixes: score 87, CLS 0,241 in oranje, alle andere metrics groen en Layout shift culprits in rood.

Voor

Lighthouse op telefoonbreedte op dezelfde pagina na de fixes: score 100, CLS 0 en Layout shift culprits onder de geslaagde audits.

Na

Voor en na de vier fixes: Lighthouse 13.5, mobiel, gesimuleerde throttling.

Sommige shifts komen uit plekken waar je niet bij kunt: een ad slot dat je advertentiepartner vult, een app die een widget injecteert of een shift die alleen echte bezoekers na het scrollen zien. In een Core Web Vitals power hour lopen we ze na in je field data en een opname van je pagina, en bepalen we welke container welke hoogte krijgt.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

 
Arjen Karel

Een snellere site nodig?

Ik ben Arjen en ik maak al 17 jaar trage sites snel. Loop je tegen iets aan dat je zelf niet opgelost krijgt? Stuur me je URL, dan praten we even.

Stuur me je URL
Arjen Karel
Arjen Karel, Web performance consultant sinds 2009

Waarom werkt onze fix niet?

Soms serveert het CDN nog de oude HTML, of de fix zit in het desktop-template terwijl mobiel een ander template gebruikt. Stuur me je URL en het probleem, dan stuur ik je een paar momenten. Ik bekijk eerst je field data, daarna besteden we een uur aan je site, en binnen een dag heb je mijn notities op schrift. €350 excl. btw.

Stuur me je URL

Geen developer om de fix te bouwen? Ik doe het werk zelf, op een kopie van je site.

Los Layout shift culprits op in Lighthouse en PageSpeed Core Web Vitals Los Layout shift culprits op in Lighthouse en PageSpeed