If you’ve run your Shopify store through PageSpeed Insights or checked the Core Web Vitals report in Google Search Console and seen a red or amber Cumulative Layout Shift (CLS) score, you’ve probably also seen the vague advice that usually comes with it: “reserve space for images,” “avoid inserting content above existing content.” True, but not especially useful when you’re staring at a theme with dozens of sections and no idea which one is actually the culprit.
In our experience auditing Shopify themes for performance issues, CLS almost never comes from some exotic custom section a developer built five years ago. It comes from a small, repeatable set of theme elements that nearly every Shopify store uses: the announcement bar at the top of the page, product and collection images that load in without a reserved size, custom web fonts swapping in after the page has already rendered, and sticky headers or cart drawers that shove content around when they appear.
This post walks through exactly why each of these causes layout shift on Shopify specifically, not generic web performance theory, and gives you a practical sequence for finding and fixing them on your own theme.
What Cumulative Layout Shift Actually Measures
CLS is one of Google’s three Core Web Vitals (alongside Largest Contentful Paint and Interaction to Next Paint). It measures how much visible content unexpectedly moves around while a page is loading or shortly after, technically, it’s a score calculated from the size of the shifting element and how far it moved, summed across every unexpected shift in a session.
A “good” CLS score is under 0.1. Anything above 0.25 is flagged as poor, and it’s one of the more common reasons a Shopify store fails the Core Web Vitals assessment in Search Console even when Largest Contentful Paint and load times look reasonable.
The reason CLS is worth fixing isn’t just the Google ranking signal (which is real but modest). It’s the experience it’s measuring: a shopper starts reading a product description or reaches to tap “Add to cart,” and the page jumps because an image finished loading or a banner appeared above it, genuinely annoying on mobile, and a believable reason for someone to bounce before they’ve engaged with the page at all.
Shopify stores are particularly prone to CLS because so much of the layout is assembled from third-party pieces, apps that inject announcement bars, review widgets, and popups; theme sections built by different developers over time; and Liquid templates that render before all the assets they depend on (fonts, images) have actually loaded. Each is a separate opportunity for something to shift after the initial paint.
The Announcement Bar Problem
The announcement bar, that thin strip above the header advertising free shipping, a sale, or a shipping cut-off date, is one of the most common CLS offenders on Shopify, and it’s rarely caused by the theme’s native announcement bar setting. It’s usually caused by an app.
Here’s the mechanism: many announcement bar and free shipping bar apps work by injecting a <script> tag that builds the bar with JavaScript after the rest of the page has already rendered. If that bar is inserted at the top of the page, above the header, above the hero image, above everything, every element below it gets pushed down the moment the app’s script fires. That’s a textbook layout shift, and because it happens near the top of the viewport, it scores heavily against your CLS number.
The fix has two parts, and both matter:
- Reserve space for the bar before it loads. If you’re using a theme-native announcement bar (set through the theme editor’s header settings rather than an app), it typically renders inside the initial server-side HTML, which avoids this problem entirely, Shopify sends it down with the rest of the layout rather than injecting it afterwards.
- If you’re using an app-based bar, check whether it supports a fixed-height placeholder or a “reserve space” setting, and set the container’s height in your theme CSS (a simple
min-heighton the wrapper the app targets) so the layout doesn’t need to move once the script executes.
If your store runs two or three marketing apps that all inject banners near the top of the page (a free shipping bar, a countdown timer, an announcement app), the shifts compound. Consolidating into one native or well-behaved app is often the single biggest CLS win available on a typical Shopify theme.
Lazy-Loaded Images Without Reserved Dimensions
Lazy loading images is good practice, it defers offscreen images until the shopper scrolls near them, which improves initial load time. The problem isn’t lazy loading itself; it’s lazy loading without telling the browser how much space the image will take up once it arrives.
When an <img> tag has no width and height attributes (or no aspect-ratio set via CSS), the browser doesn’t know the image’s dimensions until the file has actually downloaded. Before that, it renders the image at zero height. The moment the image loads, it snaps into its real size, pushing everything below it down the page. On a product listing or collection grid with a dozen lazy-loaded images loading in at slightly different times, this can produce a cascade of small shifts that add up to a poor CLS score.
Shopify’s own theme architecture (built on the responsive image_tag and image_url Liquid filters) is generally good about this in up-to-date themes, native Shopify image tags include width and height attributes by default, which lets the browser reserve the correct space before the file loads. Where this breaks down is usually one of:
- Custom sections or older theme code that outputs raw
<img src="...">tags without dimension attributes, often hand-coded before responsive image best practice was standard. - Apps that inject their own images (upsell widgets, review photo galleries, “recently viewed” carousels) using their own markup, which may not set explicit dimensions.
- A custom theme built or heavily customised a few years ago, before Shopify’s Online Store 2.0 image handling matured.
The practical fix: every <img> tag should have width and height attributes (or a CSS aspect-ratio on its container) matching the image’s actual proportions, so the browser reserves the right space immediately, even before the lazy-loaded file has downloaded. This is usually a quick audit through theme.liquid and your key templates checking for bare <img> tags without dimensions.
Custom Web Fonts and the Font-Swap Shift
This one catches a lot of stores that have gone to the trouble of setting a custom brand font in Online Store > Themes > Customize > Theme settings > Typography, because it’s easy to assume a font change is a purely visual decision with no performance cost.
Here’s what actually happens: the browser renders the page’s text in a fallback system font first (because it hasn’t downloaded your custom font file yet), then swaps to your chosen font once it arrives. If your custom font has noticeably different letter widths, line heights, or character spacing than the fallback, that swap changes how much space the text takes up, headlines wrap differently, paragraphs run longer or shorter, and everything below that text block shifts. This is sometimes called FOUT (flash of unstyled text), and the layout shift it causes is a genuine, measurable contributor to CLS, particularly on text-heavy pages like the homepage or a blog post.
A few things help here:
- Choose a fallback font with similar metrics to your custom font where possible, so the swap causes less visual movement even if it’s not eliminated entirely.
- Use
font-display: swapdeliberately, and understand the trade-off, it avoids invisible text while fonts load, but it’s the mechanism causing the visible swap. The alternative,font-display: optional, avoids the shift by simply not swapping fonts if they don’t load in time, which suits stores where layout stability matters more than exact font consistency. - Preload the font file if your theme supports it, so it arrives earlier and any swap happens before the shopper starts reading, rather than mid-scroll.
- Limit the number of font weights and custom fonts you’re loading, each additional weight is another file that has to download before it can render.
If you’re using one of Shopify’s default theme fonts rather than a fully custom upload, this is far less of an issue, since Shopify serves those through its own optimised font delivery. The risk climbs specifically when a store has uploaded a custom brand font through theme settings without checking how it behaves during load.
Sticky Headers and Cart Drawer Animations
The last common offender is more about interaction than initial page load, but it still counts towards CLS if it happens within the measurement window, which covers the whole page session, not just the first paint.
Sticky headers that change height on scroll, for example, one that shrinks or gains a shadow once the shopper scrolls past the hero, can cause a shift if the transition isn’t handled with a smooth CSS transform rather than a change to the document’s actual layout height. Cart drawers that slide in on “Add to cart” can cause the same problem if the drawer is inserted into the normal document flow instead of being positioned as an overlay (position: fixed or position: absolute, taken out of flow so it doesn’t push other elements around).
These are lower-frequency contributors than the announcement bar and image issues above, but worth checking, particularly on a theme with a custom-built cart drawer or scroll-triggered header behaviour added by a previous developer.
A Practical Checklist for Diagnosing Shopify CLS
Work through these in order, they’re roughly ranked by how often they’re the actual cause on real Shopify stores:
- Run PageSpeed Insights (or Chrome DevTools’ Performance panel) on your homepage, a collection page, and a product page. CLS often varies by template, so check more than one page type.
- Look at the “Avoid large layout shifts” or layout shift cluster in the report, it will usually name the specific element that moved.
- Check every app that injects a bar, banner, or widget near the top of the page. Disable them one at a time and re-test to isolate which one is shifting the layout.
- Inspect image tags in your key templates for missing
width/heightattributes, particularly in custom sections and app-injected image blocks. - Check whether you’re using a custom uploaded font, and if so, test the page load with the font throttled (DevTools network throttling) to see how visible the swap is.
- Review your header and cart drawer behaviour for anything that changes page layout height rather than overlaying content.
- Re-test after each fix rather than changing everything at once, CLS issues often stack, and fixing them one at a time tells you which change actually moved the needle.
Most of this can be diagnosed with Chrome DevTools and PageSpeed Insights alone, you don’t need paid software to find the problem. Fixing it properly in Liquid and theme CSS without breaking anything else is where it gets more involved.
When to Bring In a Specialist
Diagnosing which element is causing a shift is usually straightforward with the checklist above. Fixing it properly, without breaking your announcement bar app’s functionality, without your custom font disappearing for accessibility reasons, without a cart drawer that suddenly doesn’t animate at all, takes more care, especially on a theme that’s been customised by more than one developer over the years.
This is exactly the kind of issue a Shopify performance optimisation engagement is built to catch and fix properly: identifying every layout-shift source across your key templates, testing fixes against real Core Web Vitals data rather than guesswork, and making sure the fix doesn’t quietly break something else in the theme.
FAQ
Does Cumulative Layout Shift actually affect Shopify SEO rankings?
Core Web Vitals, including CLS, are a confirmed Google ranking factor, though a relatively modest one compared to content relevance and overall site quality. The bigger impact is usually on conversion, a store that visibly jumps around while loading feels less trustworthy and is more likely to lose a shopper before they complete a purchase.
Why does my Shopify theme fail CLS even though I haven’t customised much?
Even a largely default theme can fail CLS if you’ve installed apps that inject announcement bars, popups, or widgets near the top of the page, or if you’ve uploaded a custom font through theme settings. Apps are the most common cause on otherwise-unmodified themes.
Will disabling lazy loading fix my CLS score?
No, and it’s not a fix worth making, disabling lazy loading can actually hurt other Core Web Vitals like Largest Contentful Paint by forcing every image to load upfront. The real fix is keeping lazy loading enabled but making sure every image has width and height attributes (or a CSS aspect-ratio) so the browser reserves the correct space before the image arrives.
How do I check which specific element is causing layout shift on my store?
Chrome DevTools’ Performance panel and the Lighthouse report (built into PageSpeed Insights) both identify the specific DOM elements involved in layout shift clusters, usually with a screenshot showing the before-and-after position. That’s the fastest way to confirm exactly which section, app, or image is responsible rather than guessing.
Can a theme update alone fix CLS issues?
Sometimes, if you’re on an older theme and Shopify’s newer theme versions have improved image handling, but it’s not guaranteed. Apps, custom fonts, and custom sections layered on top of any theme can reintroduce the same CLS issues regardless of how modern the base theme is, so a theme update is worth doing but shouldn’t be treated as a complete fix on its own.
Ready to Fix It Properly?
If your Core Web Vitals report is showing a poor CLS score and you’d rather have someone confirm exactly what’s causing it than keep guessing, a Shopify audit will pinpoint the specific theme elements and apps responsible before any changes get made.