Cumulative Layout Shift measures how much your page layout unexpectedly moves while loading. The user hovers over a button, an image loads above it, everything shifts down, and they click the wrong thing. That's CLS in practice — and Google uses it as a ranking signal.
What CLS Measures — and How Google Scores It
The CLS score is the product of two fractions: the impact fraction (how much of the viewport the shifting element occupied) multiplied by the distance fraction (how far it moved). Your page's CLS is the largest burst of individual shifts within any 5-second window.
Google evaluates CLS at the 75th percentile of page loads, using field data from the last 28 days. A clean Lighthouse score in the lab means nothing if one-in-four real users triggers a late-loading ad that drops your content 200px.
The Five Most Common Causes of Layout Shift
CLS has a short list of usual suspects. Audit these five before reaching for anything else.
- Images without explicit width and height — the browser has no idea how much space to reserve before the download finishes
- Ads, embeds, and iframes injected without reserved space — content arrives after paint and shoves existing elements down
- Web fonts causing FOUT or FOIT — text reflows when the custom font swaps in, shifting surrounding layout
- Dynamically injected content (cookie banners, sticky headers, lazy-loaded recommendation blocks) inserted above existing content mid-load
- Animations using margin, top, or width instead of transform and opacity — these trigger a full layout recalculation on every frame
How to Debug CLS Before It Reaches Users
Chrome DevTools Performance tab records layout shifts in the Experience track. Each red bar is a shift event — click it to see the individual score and the affected elements. Enable Layout Shift Regions under the Rendering panel to overlay shifted areas in blue directly on the page while it loads.
Lighthouse gives you a lab CLS score. Use it to catch obvious regressions, not as your production ground truth. The real number is the 75th percentile from actual users on actual devices — which you only get from field data. The gap between lab and field CLS is often bigger than any other Web Vital.
Fixing CLS Root Causes in React and Next.js
Most CLS fixes are one-liners once you've found the cause. The patterns worth knowing:
- Set explicit width and height on every <img> — or use next/image which handles this automatically and prevents the #1 cause of CLS
- Use the CSS aspect-ratio property to reserve space for embeds and iframes before they load
- Use next/font to preload fonts and eliminate FOUT entirely — no swap, no shift
- Add min-height or skeleton placeholder containers for anything that loads dynamic content after the initial render
- Replace margin/top/left animations with transform: translate() — animations that don't trigger layout never contribute to CLS
Tracking Real-User CLS in Production
Lab tools don't catch everything. A third-party chat widget loading after 3 seconds, a cookie banner injected by a consent management platform, a recommendation block from your A/B testing tool — none of these reliably show up in Lighthouse. But they hit every real user.
Once you've fixed what you can see, the remaining CLS is invisible until someone measures it in the wild. You need real-user monitoring that collects Web Vitals from actual sessions and aggregates them at the page level — not just site-wide.
// app/layout.tsx — Next.js App Router
import { StatvisorAnalytics } from "@statvisor/sdk/react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
{/* Tracks CLS, LCP, FCP, INP, and TTFB per page automatically */}
<StatvisorAnalytics frontendKey={process.env.NEXT_PUBLIC_STATVISOR_KEY!} />
</body>
</html>
);
}Statvisor's <StatvisorAnalytics /> component tracks all five Core Web Vitals — including CLS — per page from real user sessions, with no custom observer code. You get per-page percentile breakdowns without standing up a separate RUM stack.
Track the 75th percentile, not the mean. A CLS mean of 0.05 can hide a p75 of 0.3 if a subset of users consistently triggers a slow-loading third-party script. Google's ranking algorithm uses p75 field data. Your dashboards should too.
Ready to monitor your API in production?
Statvisor gives you latency percentiles, error rates, and request volume for every route — in minutes, not days.
Get started free →