The common belief is that CLS only cares about what happens above the fold on initial page load. This is wrong in two important ways. First, CLS measures layout shifts throughout the entire page lifecycle, including during and after scrolling. Second, the CLS score reported to CrUX is the worst session window across the entire user session — not just the initial viewport state. A page that loads perfectly but produces layout shifts when the user scrolls to a section with lazy-loaded ads or dynamically injected content will still fail CLS in field data, even though lab testing of the initial viewport shows a clean score.
CLS Measurement Spans the Full Page Lifecycle, Not Just Initial Load
The Layout Instability API observes every layout shift from navigationStart until the page is unloaded or the tab becomes hidden. This observation scope is not limited to the initial page load event, the DOMContentLoaded event, or any other loading milestone. Shifts that occur during scrolling, after user clicks, during infinite scroll content injection, when sticky navigation repositions, or when a chat widget expands minutes after page load are all captured and scored.
The CLS metric applies the session window algorithm to the full set of observed shifts and reports the maximum session window score across the entire page lifecycle. A page that produces zero shifts for the first 30 seconds of a user session but then generates a 0.18 burst of shifts when the user scrolls to a widget-heavy section reports 0.18 as its CLS score. That single burst determines the page’s CLS for that session.
CrUX collects this full-lifecycle score from real Chrome sessions on Android. The 75th percentile of these scores across the origin’s URL population and user base becomes the value reported in CrUX and used for the page experience ranking signal. There is no filtering for “above-the-fold” or “during initial load” — the field CLS score reflects whatever the worst session window was during each user’s entire visit.
This lifecycle-scoped measurement was an intentional design decision. When CLS was first introduced, it summed all shifts across the entire page duration, which unfairly punished SPAs and pages with infinite scroll. The session window algorithm (introduced in 2021) replaced total accumulation with maximum-window scoring, which is fairer to long-lived pages while still capturing post-load instability events. The misconception that CLS only measures initial-viewport shifts likely stems from confusion between the old cumulative approach and the current session window approach, combined with the fact that lab tools primarily report initial-load CLS.
How Scroll-Triggered Content Creates Below-the-Fold CLS
Several common implementation patterns produce layout shifts that only manifest when users scroll past the initial viewport. Each pattern involves content that is not present or not fully rendered at page load and then causes element movement when it appears.
Lazy-loaded images without reserved dimensions are the most frequent below-fold CLS source. When an image with loading="lazy" enters the viewport without explicit width and height attributes or CSS aspect-ratio, the browser inserts the image with zero dimensions and then expands it to its natural size once loaded. Every element below the image shifts downward. This shift occurs only when the user scrolls to the image’s position, making it invisible during initial-load CLS measurement.
Dynamically injected ad slots that fill after scrolling into view produce viewport-dependent CLS. Ad containers that rely on intersection observer triggers to request and render ads create shifts when the ad creative loads and expands the container from its placeholder size (often zero or a min-height) to the creative’s actual dimensions. These shifts cluster at scroll positions where ad slots exist.
Infinite scroll content injection appends new content blocks to the page as the user scrolls. If the injection changes the height of elements above the scroll position (for example, by inserting content above the current viewport position), surrounding content shifts. Even injection below the current viewport can cause shifts if it triggers re-layout of previously rendered content.
Intersection-observer-triggered widget rendering defers rendering of heavy components (social embeds, video players, interactive elements) until they approach the viewport. The render event produces a shift if the component’s container grows from its placeholder size to its rendered size.
Why Lab Testing Misses Below-the-Fold CLS by Default
Lighthouse and most synthetic performance tools evaluate CLS for the initial page load period only. Lighthouse does not scroll the page, does not simulate user interaction, and terminates its measurement after a quiet window following the load event. This means Lighthouse’s CLS score captures only shifts that occur during the initial rendering phase — above-the-fold content loading, font swaps, and image/ad rendering in the initial viewport.
The DebugBear analysis of CLS lab vs. field differences confirms this structural gap: lab tools measure a fundamentally different slice of the CLS timeline than field data captures. The field CLS is almost always higher than lab CLS because real users interact with the page — they scroll, click, hover, open menus, and trigger content loading events that lab tools never exercise.
Some lab tools offer scroll simulation capabilities. WebPageTest can be configured with custom scripts that scroll the page. Lighthouse CI can run scripts that interact with the page before measuring. Chrome DevTools allows manual scrolling during a Performance panel recording. But these simulated scroll patterns are deterministic and typically follow a single path, while real users scroll at different speeds, to different depths, and with different timing relative to content loading. A lazy-loaded image that shifts at a specific scroll position may only cause a shift if the user scrolls at a speed that causes the image to enter the viewport before it finishes loading — a timing dependency that synthetic scrolling may not replicate.
The result is that sites can report passing CLS in every lab test while consistently failing CLS in CrUX field data. This discrepancy is one of the most common sources of confusion for teams monitoring Core Web Vitals, and it is almost always caused by below-the-fold content behavior that lab tools structurally exclude from their measurement.
Full-Lifecycle CLS Attribution and Controlling Third-Party Content Shifts
Identifying below-the-fold CLS sources requires RUM instrumentation that captures each layout shift with its timestamp, the affected DOM elements, and the user’s scroll position at the time of the shift. The standard PerformanceObserver for layout-shift entries with buffered: true captures all shifts from page load through session end:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
const scrollY = window.scrollY;
const sources = entry.sources?.map(s => ({
node: s.node?.nodeName,
previousRect: s.previousRect,
currentRect: s.currentRect
}));
// Log entry.value, scrollY, sources to analytics
}
}
}).observe({ type: 'layout-shift', buffered: true });
The hadRecentInput filter is critical. Layout shifts that occur within 500ms of a discrete user input (tap, click, keypress) are excluded from CLS scoring because they are considered expected responses to user action. Importantly, scroll is not classified as discrete input — it is a continuous interaction. This means layout shifts triggered by scrolling (lazy-loaded content popping in, scroll-triggered ad rendering) are counted toward CLS, even though the user’s scroll action caused them. This distinction is frequently misunderstood.
Filtering the logged shifts by scrollY value isolates below-fold contributions. If the highest-scoring session window consists entirely of shifts that occurred at scrollY > 1000px, the CLS problem is exclusively below-fold and explains why lab testing (which measures at scrollY = 0) shows clean scores.
The web-vitals library’s attribution build provides the worst session window’s composition, including the individual shifts and their sources property identifying which DOM elements moved. Combining this with scroll position data creates a diagnostic map showing where on the page CLS problems concentrate. For long-form content sites, this map typically reveals hotspots at ad slot positions, lazy-loaded image galleries, and dynamically rendered widget sections.
In Chrome DevTools, recording a Performance trace while manually scrolling through the entire page reveals below-fold shifts in the Experience row of the timeline. The Layout Shift Regions overlay highlights shifted elements visually. This manual approach works for one-off debugging but does not scale to monitoring the full distribution of user scroll behaviors.
Below-the-fold CLS is disproportionately caused by third-party content: ad slots, social media embeds, comment widgets, recommendation engines, and dynamically injected banners. Site owners have limited control over the rendering behavior of these elements. The ad network decides the creative dimensions. The social embed decides its own height based on content. The recommendation widget renders asynchronously with unpredictable timing.
The achievable strategy focuses on space reservation and containment rather than elimination. For ad slots, reserving min-height based on the most commonly served creative height at each breakpoint prevents the largest shifts. For social embeds, applying contain: layout with min-height based on typical embed dimensions isolates the widget from the page layout. For lazy-loaded images, ensuring every <img> element has width and height attributes (or parent containers with aspect-ratio) prevents image-loading shifts entirely.
Eliminating all below-the-fold CLS is not realistic for sites with significant third-party content. The operational goal is reducing the worst session window below 0.1, which is achievable with targeted space reservation and CSS containment applied to known dynamic content regions. Monitoring the worst session window composition in field data validates whether the mitigations are effective and identifies when new below-fold CLS sources appear — which happens regularly as third-party scripts update and ad configurations change.
Do infinite scroll implementations accumulate CLS as new content loads at the page bottom?
Yes. Each content injection that shifts existing visible elements contributes to CLS. Infinite scroll that appends content strictly below the current viewport without displacing any visible element does not trigger shifts. However, implementations that insert loading spinners, resize containers, or reflow existing items as new content arrives produce shifts that accumulate across session windows throughout the browsing session.
Does back-forward cache restoration reset the CLS score for a page?
Yes. When a page is restored from the back-forward cache (bfcache), Chrome treats it as a new page navigation and begins CLS measurement from zero. Any CLS accumulated before the user navigated away is reported as a separate page view. This means bfcache-restored pages start with a clean CLS slate, which can mask chronic below-fold shift issues that only manifest during extended scroll sessions.
Can lazy-loaded images below the fold cause CLS even with width and height attributes set?
Generally no, provided the width and height attributes or equivalent CSS aspect-ratio declarations accurately reflect the final rendered dimensions. The browser reserves layout space based on these dimensions before the image loads. However, if responsive CSS overrides the intrinsic aspect ratio at certain breakpoints without a corresponding aspect-ratio rule, the space reservation fails and a shift occurs when the image renders at its actual dimensions.