Performance

How to Fix CLS Caused by Ads, Before the Ads Arrive

There is a specific kind of mistake that only becomes visible after you monetise: the ad slot was added to a template that had no space reserved for it, the page reflowed on every load, and the metric that measures this has been degrading for weeks before anyone looks.

The reason it is worth reading about now rather than later is that the fix is architectural — one of six decisions to make before launch. Reserving space for an ad is trivial in a template and awkward to retrofit across a site. And the underlying metric is more subtle than the way it is usually summarised.

Getting the metric right first

Core Web Vitals are currently LCP, INP, and CLS — all assessed at the 75th percentile, segmented separately for mobile and desktop:

MetricGoodNeeds improvementPoor
LCP≤ 2.5 s2.5–4.0 s> 4.0 s
INP≤ 200 ms200–500 ms> 500 ms
CLS≤ 0.10.1–0.25> 0.25

If you have internal documents that still list FID as the interactivity metric, they are out of date. INP replaced FID, FID support was formally deprecated, and Google set a hard transition deadline of 9 September 2024 for CrUX and PageSpeed Insights API consumers. Stating FID as current is wrong, and it is a cheap way to date your advice.

Note also that CLS has no unit. It is not milliseconds and it is not a percentage. A score of 0.1 is the threshold, and it accumulates, which is what makes the next section matter.

How CLS is actually calculated

This is the part most explanations get wrong. CLS is not the sum of every layout shift on the page.

It is the largest session window: shifts are grouped into bursts, consecutive shifts in a burst are less than one second apart, and a burst stops growing at five seconds. The score for the page is the burst with the highest cumulative score. Note the layer this sits at — the W3C specification defines how a single shift is scored, while the bundling into session windows is Google’s scoring on top of it.

Two consequences that change how you debug:

One bad moment is the whole story. A cluster of shifts inside two seconds determines your score, regardless of how clean the rest of the page is. You do not need to fix every shift; you need to fix the worst burst.

Slow pages are not automatically punished. The 5-second cap exists partly so that a page which keeps shifting for thirty seconds does not accumulate an unbounded score. Live-updating content is not as catastrophic as it looks.

There is also one documented exclusion: layout shifts within 500 ms of a user interaction are excluded, because the user caused them and the resulting movement is expected. Shifts that happen on their own, after load, all count.

Why ads are the worst offender

Start with the mechanism, which does not depend on which network you are using. The creative that wins the auction is not known at page-parse time, and it may be any of several sizes, because ad networks and publishers routinely support dynamic ad sizes.

So the sequence is: HTML renders without the ad, the ad auction completes a few hundred milliseconds later, a slot of unknown height is inserted, and everything below it moves. Large text blocks read as deliberate; images and paragraphs shoved downwards read as broken.

This is worse than a missing image, because a missing image has a known size that you simply failed to declare. An ad’s size is decided by an auction you are not part of.

Reserve the space, and never give it back

The recommended fix is space reservation, and there is one consequence of it that gets skipped:

Reclaiming reserved space can cause as much CLS as inserting content.

If you reserve a slot and then collapse it when no ad is sold, you have moved everything on the page a second time. The safer default is to show a placeholder rather than collapsing the slot. The empty space is the price.

That is the design decision, and it is a real one: you are choosing to show an empty box to some readers in order to keep the page stable for all of them.

The techniques that actually hold a slot

TechniqueUse whenNotes
min-height on the slotThe ad has a predictable minimum sizeSimplest and most robust
aspect-ratioThe slot is adaptive but has a known shapeThe consistent recommendation for adaptive content
Media queriesSlot size varies by breakpointCombine with the above
Placeholder contentNo ad was returnedNever collapse to zero height

A reserved slot that never fills is not a wasted slot. It is a slot doing its job, which is to hold a fixed amount of space so that nothing else has to move.

What is not in the official guidance

contain-intrinsic-size is a genuinely useful CSS property for this problem, and you will find it recommended widely. It is not part of the checklists most ad-CLS guides give — those name min-height and aspect-ratio. It may well work for you, but treat it as a technique you are testing rather than one the vendor endorses.

The other thing worth flagging: no ad network publishes slot dimension specifications for this purpose. I checked. The publisher documentation from the major networks covers formats and revenue, not layout stability. There are no official recommended slot heights, which means every “ideal ad slot size” figure you read is somebody’s inference.

The font shift that gets blamed on everything else

One more contributor, and it is easy to misattribute. Every font-display value except optional can cause layout shift on swap — including swap, which is the one most performance guides recommend by default. Nothing about that is specific to one vendor’s guidance; it follows from how the swap period works.

Controls that work: font-display: optional (the only value that guarantees no swap-related shift, at the cost of possibly never showing the web font), or swap combined with early delivery plus size-adjust and the metric-override descriptors to shrink the difference between the fallback and the web font. The static site version of this is simpler — use system fonts, ship no web font, and the problem does not exist.

The number nobody will give you

Here is the trade-off, stated plainly: bigger ad units generally earn more, and bigger ad units shift layout more. Reserved space costs you nothing in revenue but occupies attention. There is no published guidance that resolves this, because it depends on your traffic, your layout, and your tolerance for empty boxes.

So you will have to decide it yourself, which means measuring it yourself: load the page with a cleared cache, watch the reserved slot, and confirm nothing below it moves when the creative arrives. Then check that the placeholder case — no ad sold — moves nothing either. The second case is the one people forget to test, and it is the one that produces the worse score.

When to accept the shift

If a slot sits below the fold and nothing beneath it moves, the shift it causes may not reach your worst session window at all, because the 5-second cap and the largest-burst rule mean a late, isolated shift is cheap. Measure before you optimise, and do not trade a good user experience for a metric nobody is failing.

Sources

Primary documentation only — the provider's own page, the specification, or the standard itself. Claims that rest on anything else are described in the text as a market pattern or an inference, not as documentation.

  1. W3C — Layout Instability (impact fraction, distance fraction, and hadRecentInput: true when the last input is less than 500 milliseconds in the past)
  2. MDN — LayoutShift (the entry attributes and the sources list)

Written by . Spotted something out of date? Tell us.