All checks
Performancemediumpoor-cumulative-layout-shift

The page moves under people while it loads

Cumulative Layout Shift measures how much the page jumps around as it loads.

0% · n=31 · as of 15 September 2026 of sites ShipReady scanned had this, counted across sites where this check completed.

Why it matters

Cumulative Layout Shift measures how much the page jumps around as it loads. Google's threshold for "poor" is 0.25, and Chrome recorded the observed value at the 75th percentile for your site: a measured share of real visits were in the poor band. This is the one users feel as the site being hostile — they go to press a button, an image finishes loading above it, everything moves down, and they press something else. On a page with a delete action or a payment button, the mis-click has consequences. The causes are almost always images and ads with no reserved space, and web fonts that reflow the text when they swap in.

How ShipReady detects it

Core Web Vitals, from the Chrome UX Report. ──────────────────────────────────────────────────────────────────────────── WHY THIS CHECK CAN EXIST AT ALL ──────────────────────────────────────────────────────────────────────────── checks/performance.py ends with a deferral note: the real Core Web Vitals — LCP, INP, CLS — are measured in a rendering browser, so a passive scanner cannot produce them, and that pillar ships only observable proxies ("images have no dimensions, which can cause layout shift", never "your CLS is 0.3"). CrUX changes the premise rather than the constraint. It is Google's public dataset of what REAL Chrome users actually experienced on a site over the trailing 28 days. We are not measuring anything and not estimating anything — we ask Google what it recorded and quote the answer. The scanner still renders nothing. That also makes this the strongest evidence in the whole scanner. Every other rule reasons from what a site COULD do to a visitor; this one reports what it DID do, to real people, in the field. ──────────────────────────────────────────────────────────────────────────── THE THREE OUTCOMES, WHICH MUST NEVER COLLAPSE INTO EACH OTHER ──────────────────────────────────────────────────────────────────────────── 1. DATA, AND IT IS POOR -> a finding per metric in the poor band. 2. DATA, AND IT IS FINE -> silence. We looked, and there is nothing to say. This is a real pass. 3. NO DATA -> ScanSignals.note_incomplete, and NO finding. Outcome 3 is the one that matters. CrUX only publishes an origin once it has enough Chrome traffic to be statistically meaningful and to preserve anonymity, so a new site, a low-traffic site, or an internal tool has no record at all — and that is the COMMON case for this product's users, who are scanning something they just launched. Returning silence for both 2 and 3 would report "Performance: no issues" for a site nobody has ever measured. That is the exact failure the coverage-caveat system exists to prevent, so no-data goes through it and the report says the metrics were not assessed. ──────────────────────────────────────────────────────────────────────────── WHY ONLY THE "POOR" BAND ──────────────────────────────────────────────────────────────────────────── Google defines three bands per metric. This reports only POOR, never "needs improvement" — a deliberate false negative, in the direction this scanner always errs. A site at LCP 3.9s is genuinely not great and will not be told so here; a site at LCP 4.6s is failing real users and will be. Reporting the middle band would roughly triple the finding count for advice that reads as "could be better", which is not what a production-readiness verdict is for. ──────────────────────────────────────────────────────────────────────────── WHAT THIS SENDS, AND WHERE ──────────────────────────────────────────────────────────────────────────── One POST per scan, to Google, containing the scanned ORIGIN and nothing else. No page URLs, no crawl contents, no visitor data — and nothing is sent to the customer's own site, so this check cannot contribute to bot mitigation. Queried by ORIGIN rather than by URL on purpose. Origin-level records are aggregated across the whole site, so they exist for far more sites than per-URL records do, and the answer describes the site rather than whichever page the crawler happened to seed on. REQUIRES `CRUX_API_KEY`. Without one this check is INERT: it returns nothing and records no caveat, because a key we have not deployed is a gap in our product, not a fact about the customer's site. Telling every reader that Core Web Vitals "were not assessed" because of our own configuration would put an unactionable caveat on 100% of reports. It logs `crux_api_key_missing` instead.

Detection is deterministic. ShipReady reports this only when it observes the condition directly, and prefers to miss a real problem over inventing one. Rule version 1.0.0.

How to fix it

This is the prompt ShipReady puts in your report — written to be pasted straight into Cursor, Claude Code, or whichever assistant built the app.

Real Chrome users are seeing the page move under them past Google's "poor" threshold of 0.25. Every cause is something that takes up space AFTER the surrounding content has already been laid out.

RESERVE SPACE FOR EVERY IMAGE AND VIDEO. Set `width` and `height` attributes (or a CSS `aspect-ratio`) on every one, including ones sized by CSS — the attributes tell the browser the shape before the file arrives, and the CSS still controls the displayed size. This is the single highest-value fix.

GIVE ADS, EMBEDS AND IFRAMES A FIXED CONTAINER with a min-height, so an empty slot does not collapse and then expand when it fills.

STOP FONTS REFLOWING TEXT. Preload the font, use `font-display: optional` or `swap`, and set `size-adjust` / `ascent-override` on the fallback face so the two metrics match. `next/font` handles this automatically.

NEVER INSERT CONTENT ABOVE EXISTING CONTENT. Banners, cookie notices and "you have unsaved changes" bars that appear at the top push everything down. Overlay them, or reserve their space from the first paint.

ANIMATE WITH `transform`, NOT with `top`, `left`, `width` or `height` — transforms do not trigger layout and do not count as a shift.

Reproduce it before you believe it is fixed: DevTools → Performance → Experience shows each shift and the element responsible, and throttling the network makes late-arriving content shift the way a real visitor sees it.

This number comes from real visitors over a trailing 28 days, so it will lag your fix by weeks. Verify in DevTools and with the web-vitals library in production rather than waiting for this figure to move — concluding the fix did not work and reverting it is the usual mistake here.

Frequently asked questions

What does "The page moves under people while it loads" mean?
Cumulative Layout Shift measures how much the page jumps around as it loads.
How serious is it?
ShipReady rates this medium. Fix soon. Meaningfully weakens a defence or degrades how the site works.
How common is this?
Across the sites ShipReady has scanned where this check completed, 0% · n=31 · as of 15 September 2026.
How do I fix it?
Paste the fix prompt on this page into Cursor, Claude Code or your AI editor. It is the same prompt ShipReady puts in your report.
Can I check my own site?
Yes — ShipReady scans up to ten pages of any public site for free and reports this alongside every other check. The free report lists every issue it finds and shows full evidence and a fix prompt for the critical and high-severity ones; medium and low findings are counted and unlock on Pro.

Related checks

Run this check on your site

ShipReady checks this and 193 other things across up to ten pages of your site, with an AI-ready fix for each. Free, no signup.