Drive Cumulative Layout Shift to Zero on Dynamic Pages
Pages jump because content arrives into space nobody reserved. Image dimensions, aspect-ratio, sized skeletons drive CLS to zero.
Everyone has done it. You go to tap a button, and a half-second before your finger lands, the page jumps. An image finished loading, a banner appeared, an ad slot expanded, and the thing you were aiming at slid out from under you. Now you have tapped something else, maybe a link you did not want, maybe a "delete." It is a small thing that feels like the page is fighting you.
That jump is Cumulative Layout Shift, and Google measures it as one of the three Core Web Vitals because it correlates so cleanly with how broken a page feels. The other two are loading, where making the LCP image load first is the lever, and responsiveness, where cutting INP below 200ms is. The target for CLS is a score of 0.1 or less at the 75th percentile of real users. The good news is that, unlike some performance metrics, layout shift is almost entirely preventable. You can drive it to zero, and the technique is the same every time: reserve the space before the content arrives.
Why the page jumps in the first place
The browser lays out the page top to bottom as content becomes available. When it hits an image tag with no dimensions, it does not know how tall that image will be, so it allocates zero space for it. The text below flows up to fill the gap. Then the image finishes downloading, the browser learns its real size, and it has to reflow the entire page to make room, shoving everything below it downward. That reflow, happening after the user can already see and interact with the page, is the shift.
Three sources account for nearly all of it:
- Images without dimensions cause roughly 60 percent of layout shifts. They are the single biggest offender by a wide margin.
- Web fonts that swap in and reflow the text account for about 25 percent.
- Dynamically inserted content, ads, widgets, recommendations, anything injected after the initial render, makes up the remaining 15 percent.
Fix those three categories and you have fixed almost all of your CLS. Here is how to fix each one properly.
Images: always reserve the box
The fix for images is the oldest trick in web development, abandoned for years and now essential again: put width and height attributes on every image and video element.
<img src="/hero.jpg" width="1200" height="675" alt="..." />
Modern browsers use those attributes to compute an aspect ratio and reserve exactly the right amount of vertical space before the image loads. The image arrives into a box that is already the correct size, so nothing reflows. Crucially, this works even with responsive CSS. If your stylesheet sets width: 100%; height: auto, the browser still honors the aspect ratio from the attributes, scales the reserved box to the container width, and keeps the space stable. You are not locking the image to a fixed pixel size; you are telling the browser the shape so it can reserve the right area.
For background images and cases where attributes do not fit, use the CSS aspect-ratio property to do the same job:
.media { aspect-ratio: 16 / 9; width: 100%; }
Either way, the principle is identical. The space is reserved before the content arrives, so the content slots in instead of pushing everything around. The dimensions also pair naturally with an image pipeline that serves the perfect byte count, where each responsive variant carries its own intrinsic size.
Fonts: stop the text from reflowing
Web fonts cause shift through a swap. The browser shows a fallback font first, then swaps in your custom font once it loads, and if the two fonts have different metrics, the text reflows: lines wrap differently, headings change height, and everything below moves.
The fixes that matter:
- Preload your critical fonts so they arrive early, before the first paint of the text that uses them, shrinking the window in which a swap can happen. This sits alongside stopping render-blocking resources from stealing your first paint, since a late stylesheet and a late font both reflow the text.
- Use
font-display: optionalfor fonts where a swap is not worth a shift, or use thesize-adjust,ascent-override, anddescent-overridedescriptors on an@font-faceto make your fallback font occupy the same space as the web font. When the fallback and the real font take up identical space, the swap produces no reflow at all. - Self-host fonts where you can, so a third-party font CDN is not adding latency and a slower swap to the mix.
Dynamic content: the React problem
Dynamically inserted content is the number one CLS cause in modern JavaScript apps, and it is the one teams most often miss, because it does not show up in a quick visual check on a fast connection. Recommendations, reviews, related posts, product cards, personalization blocks, anything rendered after the initial HTML and inserted into the normal document flow, will push the content below it down when it appears.
The fix is to reserve the seat before the guest arrives:
- Use a skeleton loader that occupies the exact dimensions of the content that will replace it. This is the part people get wrong: the skeleton must match the final content's height. A skeleton that is shorter than the real content still causes a shift when the real content swaps in and turns out taller. Measure the real thing and size the skeleton to match. Done well, the same skeletons that prevent shift also make slow feel fast with optimistic UI.
- Set a
min-heighton containers that will fill with async content, so the container holds its space whether or not the content has loaded yet. - Insert below the fold or outside the flow where possible. Content that loads in a fixed-size slot, or below what the user is currently looking at, cannot shift what they can see.
For ad slots specifically, reserve the slot's maximum expected size up front. An ad that loads into a pre-sized container never shifts the page; an ad that loads into a collapsed container expands and shoves everything down.
The small fix that catches a surprising number of shifts
One detail trips up many otherwise-stable pages: the scrollbar. On a page that starts short and grows taller as content loads, the scrollbar appears partway through, and on some platforms its appearance narrows the viewport, reflowing the entire layout by the width of the scrollbar. The one-line fix:
html { scrollbar-gutter: stable; }
This reserves the scrollbar's width from the start, so the page stays the same width whether the scrollbar is present or not. It is a tiny change that eliminates a whole category of subtle shift.
Measure it where it actually happens
CLS is a field metric. It is measured from real user sessions, on real connections, where the timing of when each piece loads relative to when the user interacts is what determines the score. A fast development machine on fast wifi may never see the shift that a customer on a slow connection hits every time, because on the slow connection the image loads after the user has already started reading.
So instrument it. The same web-vitals library that reports your other Core Web Vitals reports CLS from real sessions, and it gives you the field number that Google actually ranks on. This is exactly why your Lighthouse score lies and field data tells the truth. Reserve space everywhere, then watch the field data confirm the shift is gone for the users who were experiencing it.
This is part of how we build websites that feel solid rather than twitchy, and it is the kind of detail our web performance work hunts down, because a page that jumps under the cursor reads as cheap no matter how good the design is. Layout stability is not a luxury polish item. It is the difference between a page that feels engineered and a page that feels like it is loading forever.
The whole technique reduces to one sentence: never let content arrive into space that was not already reserved for it. Put dimensions on images, stop fonts from reflowing, size your skeletons to the real content, and stabilize the scrollbar. Do that and the page stops jumping, the user stops tapping the wrong thing, and your CLS drops to where it belongs, at zero.






