---
title: Improve Cumulative Layout Shift (CLS) on Vercel
description: Read, diagnose, and fix Cumulative Layout Shift on Vercel using Speed Insights and Next.js best practices.
url: /kb/guide/cls-on-vercel
canonical_url: "https://vercel.com/kb/guide/cls-on-vercel"
last_updated: 2026-07-08
authors: Joel Fickson
related:
  - /docs/speed-insights/metrics
  - /docs/speed-insights
  - /docs/speed-insights/quickstart
  - /docs/speed-insights/limits-and-pricing
  - /docs/cli
  - /docs/agent-resources/vercel-mcp
  - /docs/cli/metrics
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

**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](https://vercel.com/docs/speed-insights/metrics))

## See a layout shift

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

* * *

## How CLS is scored

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](https://web.dev/articles/cls))

| 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](https://vercel.com/docs/speed-insights#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](https://web.dev/articles/cls))

* * *

## Step 1: Confirm you are collecting CLS data

You need field data before you can fix field CLS. Open the project's [**Speed Insights**](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fspeed-insights&title=Go+to+Speed+Insights) tab: charts mean it is collecting, an **Enable** button means it is not.

- **Not set up yet?** Follow the [Quickstart](https://vercel.com/docs/speed-insights/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](https://vercel.com/docs/speed-insights/limits-and-pricing) for Pro and Enterprise.
  
- **From the CLI:** an empty `data` array means nothing is being recorded.
  

`vercel metrics vercel.speed_insights.cls -a p75 --since 7d --project your-project`

* * *

## Step 2: Find the route that shifts

Open [**Speed Insights**](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fspeed-insights&title=Go+to+Speed+Insights), select **CLS**, and narrow down: ([Dashboard view](https://vercel.com/docs/speed-insights#dashboard-view))

1. **Toggle mobile and desktop.** Note which is worse.
   
2. **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.
   
3. **Read the line graph** with P90/P95 added. A step up usually lines up with a specific deploy.
   
4. **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.

* * *

## Step 3: Fix the cause

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.

### 1\. Images without reserved space

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/image**` and set both `**width**` and `**height**`. Next.js reserves space from the aspect ratio so the image cannot shift as it loads. ([next/image](https://nextjs.org/docs/app/api-reference/components/image#width-and-height))
  
- When the size is unknown (responsive or CMS images), use `**fill**` with a positioned parent, plus `sizes` and `object-fit`. ([next/image → fill](https://nextjs.org/docs/app/api-reference/components/image#fill))
  

### 2\. Web fonts that swap and reflow

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](https://nextjs.org/docs/app/getting-started/fonts), [next/font](https://nextjs.org/docs/app/api-reference/components/font))

### 3\. Late-injected UI: banners, consent bars, ads, embeds

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.

### 4\. Content inserted above existing content

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.

### 5\. Hydration that changes layout

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.

### 6\. Animations on layout properties

Animating `width`, `height`, `top`, or `margin` moves neighbors. Use `transform` (`translate`, `scale`) instead, which does not trigger layout shift.

* * *

## Step 4: Hand it to an agent (optional)

If you use a coding agent, this prompt does the investigation. It assumes the [Vercel CLI](https://vercel.com/docs/cli) is set up and tells the agent how to read CLS from the CLI itself.

`Investigate why CLS is poor for <route> on this Vercel project. Start read-only; do not edit files, change settings, or deploy without my approval. 1. Read the field data yourself: vercel metrics vercel.speed_insights.cls -a p75 --group-by route \ -f "environment eq 'production'" --since 7d --project <project> --format json Find routes above 0.1 at p75, then split the worst one by device_type. 2. Inspect the route's code for the usual causes: images without width/height, next/font and adjustFontFallback, banners/ads/embeds and other UI that mounts after load without reserved space, and skeletons that don't match final size. 3. Return the specific element and component, why it shifts, the smallest fix, its risk, and how to verify with Speed Insights after deploy.`

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](https://vercel.com/docs/agent-resources/vercel-mcp).

* * *

## Step 5: Verify after you ship

CLS is a field metric, so real confirmation comes from real users.

1. **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](https://developer.mozilla.org/en-US/docs/Web/API/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.
   
2. **Ship to preview, then production.** Speed Insights tracks both. ([Speed Insights overview](https://vercel.com/docs/speed-insights))
   
3. **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.
   
4. **Check for regressions** on other routes that share the changed component.
   

`new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (entry.hadRecentInput) continue; // ignore shifts within 500ms of input for (const { node } of entry.sources ?? []) console.log(entry.value, node); } }).observe({ type: "layout-shift", buffered: true });` * * * ## Next steps - 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](https://vercel.com/docs/speed-insights/metrics#predictive-performance-metrics-with-virtual-experience-score).
  

* * *

## Reference

- Speed Insights: [overview](https://vercel.com/docs/speed-insights) · [quickstart](https://vercel.com/docs/speed-insights/quickstart) · [metrics](https://vercel.com/docs/speed-insights/metrics) · [limits and pricing](https://vercel.com/docs/speed-insights/limits-and-pricing)
  
- Next.js: [next/image](https://nextjs.org/docs/app/api-reference/components/image) · [next/font](https://nextjs.org/docs/app/api-reference/components/font)
  
- CLI: [Vercel CLI](https://vercel.com/docs/cli) · [vercel metrics](https://vercel.com/docs/cli/metrics)
  
- Background: [Cumulative Layout Shift](https://web.dev/articles/cls) · [MDN Layout Instability API](https://developer.mozilla.org/en-US/docs/Web/API/Layout_Instability_API)