Cumulative Layout Shift (CLS) measures how much a page's visible content moves around unexpectedly while it loads. It is one of Google's Core Web Vitals, and one of the metrics Vercel Speed Insights reports from your real users. You see it as a page that jumps: text reflows as a font swaps in, content drops when an image arrives, a button moves the instant before a user taps it.
This guide assumes you already ship on Vercel. It shows you how to read your CLS in Speed Insights, find the routes that shift, fix the common causes on a Next.js App Router app, and confirm the result with real-user data.
Target: field CLS at or below 0.1 at P75, verified in Speed Insights after deploy. (Speed Insights metrics)
Two routes render the same content with the same late-loading elements. The janky one reserves no space and shifts; the stable one reserves space and holds at CLS 0.
Janky: nothing reserves space, so each late element shoves the article down. The live CLS meter climbs to about 0.28.

Janky route: the banner, promo, and image load and push the article down
Stable: the same elements arrive on the same schedule, but each slot holds its height, so nothing moves and CLS stays at 0.000.

Stable route: content fills the reserved slots without moving anything
CLS is the fraction of layout shift a user sees over the life of the page, summed over the worst burst of shifts (a "session window"). You rarely need the formula, only where you sit against the threshold. (Cumulative Layout Shift)
| CLS value | Rating |
|---|---|
| ≤ 0.1 | Good |
| 0.1 to 0.25 | Needs improvement |
| ≥ 0.25 | Poor |
Two things worth knowing before you investigate:
- Shifts within 500 ms of a tap, click, or keystroke are expected and excluded. Opening a menu or accordion does not count against you. Only unexpected shifts accrue.
- Speed Insights reports CLS at P75 and splits mobile from desktop. The two often differ because of viewport size, banners, and font load. Check both. (Dashboard view)
The usual causes are images without dimensions, web fonts that render at a different size than their fallback, and ads or widgets that resize themselves. (Cumulative Layout Shift)
You need field data before you can fix field CLS. Open the project's Speed Insights tab: charts mean it is collecting, an Enable button means it is not.
- Not set up yet? Follow the Quickstart to enable it and add
<SpeedInsights />from@vercel/speed-insights/next. Speed Insights is free on Hobby for one project (10,000 events/month included); see limits and pricing for Pro and Enterprise. - From the CLI: an empty
dataarray means nothing is being recorded.
Open Speed Insights, select CLS, and narrow down: (Dashboard view)
- Toggle mobile and desktop. Note which is worse.
- Use the Kanban board. It surfaces the routes, paths, and often the specific HTML element tied to poor scores, pointing you near what shifts. Routes under 0.5% of visits are hidden by default.
- Read the line graph with P90/P95 added. A step up usually lines up with a specific deploy.
- Check the map if a shift looks region-specific, such as a locale banner or consent UI.
Write down the route, device class, percentile, and current CLS. That is your baseline and your verification target.
Prefer route-level evidence over the project average. A project CLS of 0.08 can hide one template at 0.3.
Most CLS on a Next.js app traces to a few patterns. Inspect the flagged route for these, in rough order of how often they cause it.
An <img> with no dimensions has zero height until it loads, then pushes everything below it down. This is the most common cause.
- Use
next/imageand set bothwidthandheight. Next.js reserves space from the aspect ratio so the image cannot shift as it loads. (next/image) - When the size is unknown (responsive or CMS images), use
fillwith a positioned parent, plussizesandobject-fit. (next/image → fill)
When the fallback font is replaced by the web font, the text reflows because the two fonts have different metrics. Use next/font: it self-hosts the font and generates a metrics-matched fallback via adjustFontFallback (on by default) to keep CLS near zero. Leave that default on. (Font optimization, next/font)
Anything that mounts after hydration and sits in the normal flow pushes content down. Reserve a fixed height (or min-height) for the slot before the content arrives, or pin overlays out of flow with position: fixed/absolute so they never displace anything.
Alerts, "you have N new items", or headers prepended after load shift everything below. Reserve the slot, or insert below the fold or as an overlay.
If a server-rendered shell differs in size from the hydrated result, the correction is a shift. Make skeletons and loading.tsx fallbacks the same dimensions as the final content, and reserve space for client-fetched sections.
Animating width, height, top, or margin moves neighbors. Use transform (translate, scale) instead, which does not trigger layout shift.
If you use a coding agent, this prompt does the investigation. It assumes the Vercel CLI is set up and tells the agent how to read CLS from the CLI itself.
The metric is vercel.speed_insights.cls (query at -a p75; group by route or device_type). Run vercel metrics schema vercel.speed_insights.cls for the full dimension list. If you would rather the agent pull Vercel data live instead of via the CLI, connect Vercel MCP.
CLS is a field metric, so real confirmation comes from real users.
- Lab check first. In Chrome DevTools, reload the route with the Performance panel recording and confirm the element no longer moves. To name the exact node that shifted, run the Layout Instability API snippet below in the DevTools console. It is a diagnostic aid you run on local or a preview URL, not code you ship.
- Ship to preview, then production. Speed Insights tracks both. (Speed Insights overview)
- Watch the field data. Filter to the same route, device, and percentile you baselined and confirm CLS drops to ≤ 0.1. It is collected when users leave the page, so allow enough traffic before concluding. The line graph should step down at your deploy.
- Check for regressions on other routes that share the changed component.
- Fix a shared component (header, consent bar) once and verify across every route that uses it.
- Watch CLS over time so a future deploy that reintroduces a shift shows up as a step change.
- Catch regressions before production with a Deployment Checks integration that produces a Virtual Experience Score.
- Speed Insights: overview · quickstart · metrics · limits and pricing
- Next.js: next/image · next/font
- CLI: Vercel CLI · vercel metrics
- Background: Cumulative Layout Shift · MDN Layout Instability API