Back to Blog
Shopify Development

Fixing Layout Shift (CLS) on Shopify Collections

Alex SterlingOctober 5, 20265 min read
Fixing Layout Shift (CLS) on Shopify Collections

Your Shopify collection pages fail Core Web Vitals on CLS, and the page visibly jumps while loading.

Cumulative Layout Shift measures content moving after it has been painted. On collection pages there are usually three culprits, and they compound: each one shifts everything below it.

Symptom

  • PageSpeed Insights reports CLS above 0.1 (the "good" threshold) on collection templates.
  • Product cards jump downward as images load.
  • Text reflows once the web font arrives.
  • A promotional bar or cookie banner appears after render and pushes the page down.
  • Tapping a product occasionally opens the wrong one, because the grid moved mid-tap.

Cause

1. Images without reserved space

An <img> with no dimensions occupies zero height until it loads, then suddenly claims its full height. In a grid of 24 products this happens 24 times.

2. Font swap

A web font loading with font-display: swap renders fallback text first, then re-renders in the real font. If the metrics differ, every line reflows.

3. Injected elements

App embeds, announcement bars and consent banners inserted at the top of the DOM push the entire page down after paint. This is the largest single contributor on most stores.

Fix

1. Reserve image space with width, height and aspect-ratio

{%- comment -%}
  width/height give the browser an aspect ratio before the file arrives, so the
  space is reserved from first paint.
{%- endcomment -%}
<img
  src="{{ product.featured_image | image_url: width: 600 }}"
  srcset="
    {{ product.featured_image | image_url: width: 400 }} 400w,
    {{ product.featured_image | image_url: width: 600 }} 600w,
    {{ product.featured_image | image_url: width: 900 }} 900w
  "
  sizes="(min-width: 990px) 25vw, (min-width: 750px) 33vw, 50vw"
  width="{{ product.featured_image.width }}"
  height="{{ product.featured_image.height }}"
  alt="{{ product.featured_image.alt | escape }}"
  loading="lazy"
>

Then lock the ratio in CSS so mixed portrait/landscape products do not vary:

.product-card__image {
  aspect-ratio: 1 / 1;
  width: 100%;
  height: auto;
  object-fit: cover;
}

One caveat: do not set loading="lazy" on images above the fold. It delays the LCP image and trades a CLS win for an LCP loss.

2. Stop the font from reflowing

<link
  rel="preload"
  as="font"
  type="font/woff2"
  href="{{ 'yourfont.woff2' | asset_url }}"
  crossorigin
>
@font-face {
  font-family: 'YourFont';
  src: url('yourfont.woff2') format('woff2');
  font-display: optional;      /* no swap, no reflow */
  size-adjust: 96%;            /* tune so fallback metrics match */
}

font-display: optional is the strongest anti-CLS setting: if the font is not ready almost immediately, the browser keeps the fallback for that page load rather than swapping mid-render.

3. Reserve space for injected elements

If a banner will appear, hold its height from the start:

.announcement-bar-slot {
  min-height: 44px;   /* the banner's real height */
}

@media (max-width: 749px) {
  .announcement-bar-slot { min-height: 60px; }  /* wraps to two lines */
}

An empty 44px strip is invisible to users and costs nothing. A 44px jump costs you the metric.

For consent banners, overlay rather than push:

.consent-banner {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 100;
}

A fixed-position element is outside normal flow and contributes no CLS at all.

Verify

Measure in the field, not just the lab — CLS is cumulative over the page's life, so a shift on scroll still counts:

  1. Chrome DevTools → Performance → tick "Web Vitals", record, and scroll the full collection.
  2. Look for the red CLS markers and expand each to see which node moved.
  3. PageSpeed Insights on a real collection URL, not the homepage.
  4. After deploying, check Search Console → Core Web Vitals for field data — it lags ~28 days.
// Log every shift and the element responsible, in the console.
new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.hadRecentInput) continue;      // user-initiated shifts do not count
    console.log('shift', entry.value.toFixed(4), entry.sources?.map(s => s.node));
  }
}).observe({ type: 'layout-shift', buffered: true });

Prevent

  • Every <img> gets width and height. Make it a review rule.
  • Audit app embeds before installing. Anything that injects at the top of <body> is a CLS risk.
  • Re-test after every app install, not just after theme changes.
  • Watch collection pages specifically — they repeat any per-card mistake dozens of times.

Frequently asked questions

Does CLS affect rankings?

It is part of Core Web Vitals, which is a ranking signal — but a modest one. The stronger argument is conversion: a grid that jumps while someone is tapping loses the sale.

Why does my lab score pass but field data fail?

Lab tests load once at a fixed viewport. Real users scroll, trigger lazy-loaded content and see consent banners. Always trust field data.

Can I fix CLS caused by a third-party app?

Usually by reserving space for it, as above. If the app injects unpredictable heights, that is worth raising with the developer — or replacing the app.

Need a hand?

We optimise Shopify store performance for merchants in the USA, UAE, Saudi Arabia, Qatar and the UK. If Core Web Vitals are failing, get a free audit or see our Shopify speed optimization service.

Share
#Shopify CLS fix#Shopify Core Web Vitals#Shopify collection page speed#Shopify layout shift#Shopify image aspect ratio#Shopify speed optimization
Y

The Yaxify Team

Shopify & Web Development Agency

We build custom Shopify stores and high-performance websites for growing businesses across the USA, UK, and UAE. Everything we publish comes out of client work we have actually shipped.

More about Yaxify →

Ready to start your project?

Get a free consultation and custom quote within 24 hours

No commitment required • Response within 2 hours