Fix CLS on Content-Heavy Pages, Fonts, Ads, and CSS

Learn exactly which CSS, font-loading, and ad-slot patterns cause layout shift on content-heavy pages, and how to fix each one for good CLS scores.

Fix CLS on Content-Heavy Pages, Fonts, Ads, and CSS

A page that scores 0.02 CLS in staging can ship at 0.35 in production. The difference is almost never a mystery bug. It is a handful of repeatable patterns: late-loading fonts reflowing headlines, ad slots that inject without reserved space, and images or embeds that resize after paint. Diagnosing layout shift on content-heavy pages means isolating which element moved, when it moved, and why the browser did not know its size in advance.

This article walks through the specific CSS, font-loading, and ad-slot patterns behind poor CLS scores, then shows the fixes that survive real traffic.

Get a performance audit that pinpoints every layout shift source on your site.

Request a CLS audit

What CLS actually measures (and why content-heavy pages fail)

Cumulative Layout Shift quantifies how much visible content moves after it first renders. Google’s threshold: anything above 0.1 is “needs improvement,” and above 0.25 is “poor.” The metric is sessionized, meaning it captures the worst burst of shifts within a five-second window during the page’s entire lifecycle. For a deeper primer on the metric itself, see our Core Web Vitals overview.

Content-heavy pages are disproportionately affected because they have more elements that can shift. A long-form editorial page might contain 15 images, 3 ad slots, 2 embedded videos, a newsletter signup module, and a sticky nav. Each one is a potential source of layout instability. The more elements on the page, the more coordination the CSS must do before paint.

Field data (what Chrome users actually experience) often diverges sharply from lab data (what Lighthouse measures). The reason: lab tools load pages on fast connections with no ads. Real users get third-party scripts, consent banners, and variable network speeds. Always validate CLS with Chrome User Experience Report field data, not just Lighthouse.

Font loading: the silent reflow machine

Web fonts are the single most common cause of layout shift on text-heavy pages. Here is the mechanism. The browser first renders text using a fallback font (say, Arial). When the web font file finishes downloading, the browser swaps it in. If the web font has different character widths, line heights, or letter spacing, every line of text reflows. On a 2,000-word article, that reflow can push every element below the fold downward by dozens of pixels.

The CSS property font-display controls swap behavior. Setting font-display: swap guarantees text is visible immediately but creates a visible reflow when the font arrives. Setting font-display: optional tells the browser to use the web font only if it arrives within roughly 100 milliseconds; otherwise, it sticks with the fallback for that page load. This eliminates the reflow entirely at the cost of occasionally showing the fallback font.

The highest-impact CLS fix for editorial sites is usually not an image or an ad. It is matching your fallback font metrics to your web font using CSS size-adjust, ascent-override, and descent-override descriptors, so the swap causes zero reflow.

Here is how to build a metric-compatible fallback:

Self-hosting font files (rather than loading from Google Fonts) also helps because you control caching headers and can preload the WOFF2 files with <link rel="preload">. Preloading does not eliminate the swap, but it narrows the window in which the fallback is visible, reducing the probability of a shift appearing in the CLS window.

1

Identify your web font’s metrics:

Use a tool like Fontaine or the @font-face descriptor approach to extract the ascent, descent, and line-gap values of your primary typeface.

2

Create an adjusted fallback declaration:

In your CSS, define a @font-face for your fallback (e.g., Arial) with size-adjust, ascent-override, descent-override, and line-gap-override values that match the web font’s metrics as closely as possible.

3

Set the adjusted fallback in your font stack:

Reference this adjusted fallback in your font-family declaration so the browser uses it before the web font loads.

4

Validate with DevTools Layout Shift regions:

Open Chrome DevTools, enable "Layout Shift Regions" under Rendering, and do a hard reload. If the text areas no longer flash green on font swap, the reflow is eliminated.

Images, videos, and the missing aspect ratio

Every image or video without an explicit aspect ratio is a layout shift waiting to happen. The browser cannot reserve the correct vertical space until the resource’s dimensions are known. If the HTML lacks width and height attributes (or the CSS does not set an aspect ratio), the element starts at zero height, then expands when the file header arrives.

The fix is straightforward but requires discipline across the entire content pipeline. Set width and height attributes on every <img> and <video> tag. Modern browsers use these attributes to calculate an intrinsic aspect ratio before the file loads. Pair this with max-width: 100%; height: auto; in CSS so the image remains responsive. This approach gives you zero-shift, fully responsive images with no JavaScript.

Embedded iframes (YouTube, Vimeo, social posts) are trickier. An iframe has no intrinsic dimensions. Wrap each embed in a container with a fixed aspect ratio using the aspect-ratio CSS property or the older padding-bottom technique. For YouTube, aspect-ratio: 16 / 9 on the wrapper is sufficient. For variable-height embeds like tweets, you may need JavaScript to resize the container after the embed renders, but the initial placeholder must still reserve a reasonable minimum height.

If your CMS allows editors to paste embed codes directly, that is where the process breaks. The CMS needs to wrap those embeds in aspect-ratio containers automatically, either through a server-side filter or an editor plugin. Otherwise, every new article is a CLS regression risk. A well-structured performance budget should include CLS thresholds per template, enforced in CI.

Ad slots: the hardest problem in CLS

Ads cause layout shift because their dimensions are unknown until the ad server responds. A slot might receive a 300×250 creative, a 300×600 creative, or no fill at all. The page cannot know which outcome to plan for.

The most effective pattern is to reserve the maximum possible height for each slot. If a slot can serve either a 250px or 600px tall unit, reserve 600px. If the shorter ad wins, you have blank space, which is a business decision, not a performance one. Many publishers prefer a smaller visual gap over a large CLS penalty that tanks their search rankings.

For slots that might receive no fill, collapse the container only before layout paint, never after. Use min-height on the slot container, and if the ad server returns no fill, remove the container with a class toggle that triggers min-height: 0 before the element has been painted to the viewport. If the slot is below the fold and has not yet scrolled into view, collapsing it is safe because it has not contributed to the layout context the user has seen.

The CLS specification only penalizes shifts of elements that are visible in the viewport at the time of the shift. Reserving and collapsing ad slots that are far below the fold has no CLS cost, as long as the user has not scrolled to them yet.

Sticky or anchor ads (units fixed to the bottom of the viewport) do not cause layout shift if they are positioned with position: fixed or position: sticky, because fixed and sticky elements are removed from the normal document flow. However, a sticky ad that pushes the page content up by inserting a spacer div at the bottom will cause shift. Audit your ad library’s implementation carefully.

Late-loading ad scripts are another vector. If the ad library’s JavaScript loads asynchronously and injects containers into the DOM after initial paint, every insertion is a shift. The fix: define all ad containers in the server-rendered HTML with their reserved dimensions. The ad script should populate those existing containers, not create new ones. Google’s Publisher Tag documentation explicitly recommends this pattern.

CSS patterns that create invisible shift

Some layout shift sources are not media or ads. They are structural CSS choices that look stable in development but fail under real conditions.

Dynamic navigation bars. A nav that starts transparent and becomes solid on scroll is fine if its height does not change. But if the solid state adds padding, a logo, or a search bar that increases the nav’s height, every element below it shifts. Lock the nav height across states.

Client-rendered content replacing server-rendered placeholders. A React or Next.js hydration mismatch can cause a component to render at one size on the server and a different size after client-side JavaScript runs. This is especially common with “logged in” vs. “logged out” UI states, where the server renders a generic header and the client swaps in a user-specific one. If the two versions differ in height, that is a shift. For a broader look at how to manage these trade-offs, see our speed optimization guide.

Late-injected banners. Cookie consent bars, promotional banners, and notification bars that insert at the top of the page after load push all content downward. Use position: fixed or position: sticky for these elements so they overlay the page rather than displacing it. Alternatively, render the banner in the initial HTML so it is present before first paint.

CSS @import chains. A stylesheet that @imports another stylesheet creates a waterfall. If the second stylesheet redefines element dimensions (grid columns, padding, font size), the browser repaints with new dimensions after the second file arrives. Flatten your CSS imports into a single file or use bundler-level concatenation.

Diagnosing shift in the field

Lab tools catch the obvious problems. Field data catches the rest. Here is a practical diagnostic sequence:

Do not trust a single Lighthouse run. CLS is inherently variable because it depends on load timing, which changes with network conditions, server response time, and third-party script behavior. Run at least five traces and look at the median.

1

Pull CrUX data by page template:

Use PageSpeed Insights or BigQuery against the Chrome UX Report to compare CLS scores across your article template, category pages, and landing pages. Identify which template type has the worst 75th-percentile CLS.

2

Record real sessions with Layout Instability API:

Deploy a small script that listens for layout-shift entries via the Performance Observer API and logs the shifting element’s selector and the shift value. Aggregate this data in your analytics platform.

3

Reproduce in DevTools:

Open the worst-performing page, throttle to "Slow 3G" in the Network panel, enable "Layout Shift Regions" in the Rendering tab, and record a Performance trace. The trace shows exactly which element shifted, when, and by how many pixels.

4

Fix, deploy, and validate against field data:

CrUX data updates on a rolling 28-day cycle. After deploying a fix, wait at least two weeks before trusting the field score. In the meantime, use the Layout Instability API logs to confirm the shift is gone in real sessions.

After the fix: keeping CLS stable

The hardest part of CLS is not fixing it once. It is preventing regressions. A new ad partner, a redesigned header, a CMS plugin update: any of these can reintroduce shift. Build CLS assertions into your CI pipeline using tools like Lighthouse CI or web performance monitoring dashboards that alert on threshold breaches. Set the threshold at 0.05, not 0.1, so you catch regressions before they reach “needs improvement” territory.

Every template change should require a CLS check on at least three representative pages before merge. Treat layout stability the way you treat unit tests: something that blocks the deploy if it fails.

FAQs

What is a good CLS score for content-heavy pages?

Google considers any score at or below 0.1 as “good.” For content-heavy pages with ads and embeds, aim for 0.05 or lower to build a buffer against regressions from third-party scripts or CMS content changes.

Does font-display swap always cause layout shift?

Yes, if the web font’s metrics differ from the fallback font’s metrics. The swap triggers a text reflow. You can eliminate this shift by using CSS font metric override descriptors (size-adjust, ascent-override, descent-override) to match the fallback font’s dimensions to the web font’s dimensions.

How do I prevent ads from causing CLS?

Reserve the maximum possible height for each ad slot in your HTML and CSS before the ad script loads. Define all ad containers in server-rendered HTML rather than letting the ad library inject new DOM elements. Use min-height on each container and collapse unfilled slots only if they have not yet entered the user’s viewport.

Why is my CLS score different in Lighthouse versus field data?

Lighthouse measures a single, synthetic page load without third-party ads, consent banners, or variable network conditions. Field data from the Chrome User Experience Report reflects what real users experience, including late-loading scripts and slow connections. Always prioritize field data for CLS assessment.

Can lazy-loaded images cause layout shift?

Only if the image element lacks explicit width and height attributes or a CSS aspect-ratio rule. When dimensions are declared, the browser reserves the correct space before the image loads, so lazy loading does not cause any shift.

Stop shipping layout shift into production

The patterns above account for the vast majority of CLS failures on content-heavy sites. Moburst Digital Experience runs template-level CLS audits and builds the CSS, font-loading, and ad-slot architecture that keeps scores below 0.05 at scale.

Talk to a performance engineer