Fixing Layout Shift on a Shopify Store
On a Shopify store, most layout shift comes from a short list of places: product images output without dimensions by older themes, product cards and galleries whose photos are different shapes, apps that inject stars, badges and payment messages after the page loads, sections the theme fetches later, and the way theme fonts are loaded.
This guide works through each one using theme settings, Liquid and a little CSS, on a duplicate theme so your live store is not at risk. It is aimed at store owners who can edit theme code, or who can brief a developer precisely.
Where Shopify stores usually shift
Shopify pages are rendered from Liquid templates on Shopify's servers, so the first HTML is usually complete. The shifts come from what arrives afterwards: images, fonts, app scripts and sections fetched by the theme's JavaScript. Test at least one page of each template type, because they shift for different reasons.
| Template | Typical culprits |
|---|---|
| Home page | Slideshow or hero images without a fixed ratio, app-injected promotional bars, featured collection cards |
| Collection | Product cards with photos of different shapes, review stars and badges added by apps, colour swatches loaded by script |
| Product | Media gallery height changing between images, buy-now-pay-later messages, accelerated checkout buttons, reviews and upsell widgets, recommendations below |
| Cart | Upsell and shipping-progress widgets, gift wrap options, payment buttons |
| Every page | Theme fonts, announcement bars from apps, header changes on scroll |
Basket drawers and "added to cart" notifications that push content are covered in the cart and notification banner guide, so this guide concentrates on the rest.
Testing safely and isolating apps
Never experiment on the published theme. In the admin, go to Online Store, then Themes, and duplicate the live theme. Preview the copy and share its preview link with anyone helping you. All the changes below can be tested there first.
- Run the CLS Debugger on a representative home, collection and product page and note the moved elements and their scores.
- Record a throttled load in Chrome DevTools' Performance panel on a phone-sized viewport, with the cache disabled, and scroll the whole page. Product pages often shift further down, where reviews and recommendations load.
- Switch off app embeds in the duplicate theme. In the theme editor, open the App embeds panel and turn them off one at a time, re-testing after each. If CLS drops when a particular embed is off, that app is involved.
- Look for leftover app code. Older apps, and some that have since been uninstalled,
added snippets directly into
theme.liquidor product templates. Search the theme code for{% render %}tags and script tags that reference apps you no longer use.
For real-visitor data, use the Core Web Vitals report in Search Console, which groups similar URLs, and the web performance reports in the Shopify admin analytics section, which show how the store performs for real sessions. Both lag behind changes by some weeks.
Product images and galleries
Let the image_tag filter do the work
Current Shopify themes output images with the image_tag filter, which writes width
and height attributes from the image's real dimensions automatically. Older themes, and
custom sections copied from tutorials, often build the tag by hand with only a src. Replace
those with the filter:
{{ product.featured_image
| image_url: width: 900
| image_tag:
loading: 'lazy',
widths: '360, 540, 720, 900',
sizes: '(min-width: 990px) 25vw, 50vw',
alt: product.featured_image.alt }}
If you must write the tag by hand, add width="{{ image.width }}" and height="{{ image.height }}"
and let CSS set height: auto. Do not lazy-load the first large image on the page; that helps
LCP and does not affect CLS either way once dimensions are present.
Product cards with photos of different shapes
If one product is photographed square and the next is portrait, the cards in a collection grid are different heights and rows can reflow as images load. Many themes have an image ratio setting for product cards, with options such as adapt to image, portrait or square. A fixed ratio makes every card the same height before any image arrives, with images cropped or letterboxed to fit. "Adapt to image" follows each photo's own shape, which is only stable if all your photos share one.
Galleries that change height
On product pages, a gallery that sizes itself to the current image grows or shrinks when the shopper
swipes or picks a variant with a differently shaped photo. The change right after a tap is exempt from
CLS, but if the new image loads slowly and the gallery resizes more than half a second later, it counts.
Give the main media frame a fixed aspect ratio in the theme's CSS and use object-fit: contain
so every image fits the same box. Better still, photograph products at one consistent ratio.
Apps, app blocks and payment buttons
Apps add content to a store in three main ways, and each needs a slightly different fix.
- App blocks are placed by you in the theme editor, inside a section. The block's position is known, so you can reserve its space.
- App embeds are switched on in the App embeds panel and inject content through a script, often in positions the app chooses.
- Legacy code is added to theme files by an app or its support team, or loaded as a script tag. It is the hardest to control and often outlives the app.
Review stars on product cards, buy-now-pay-later messages, trust badges, stock counters, size guide
links and "frequently bought together" boxes all appear after the page has painted. Each one pushes the
content below it by its own height. Wrap app blocks in a container with a min-height equal
to the loaded widget, using the Custom CSS option that many current themes offer in section settings,
or your theme's stylesheet:
/* In the product information section's Custom CSS, or assets/base.css */
.product__info-container .shopify-app-block { min-height: 24px; }
.card__information .rating-wrapper { min-height: 20px; } /* stars on cards */
Class names differ by theme and app; inspect the loaded widget in DevTools to find the right selector and its real height on mobile and desktop.
Accelerated checkout and payment messages
The accelerated checkout buttons output by {{ form | payment_button }} are drawn by a
Shopify script after load, and which wallets appear depends on the shopper's device and region. Instalment
messaging from {{ form | payment_terms }} also loads asynchronously. Give their containers
a minimum height in the theme CSS so the add-to-cart area does not jump. Check the loaded height on an
iPhone and an Android phone, as the buttons shown can differ.
Sections the theme loads later
Some sections are not in the first HTML. Product recommendations are the classic case: themes such as Dawn render an empty container, wait until it is near the screen, then fetch the finished section from Shopify and insert it. Complementary products, recently viewed items and some predictive search panels work the same way. When the content arrives, anything below it, often the footer or a newsletter block, moves.
Because the section only loads when the shopper scrolls near it, the shift happens mid-scroll, which still counts. Reserve its height:
product-recommendations { display: block; min-height: 420px; }
@media (max-width: 749px) {
product-recommendations { min-height: 760px; }
}
Measure the loaded section at each breakpoint and use those numbers. If a product sometimes has no recommendations, the theme may remove the section, which leaves a gap closing; hiding the section with a CSS rule when empty is fine as long as that happens before it scrolls into view. Changing the recommendations to render in Liquid rather than through a fetch removes the shift entirely, at the cost of slightly slower first HTML.
Theme fonts
Fonts chosen in Theme settings, under Typography, come from Shopify's font library and are usually
written into the page in layout/theme.liquid with the font_face filter:
{{ settings.type_body_font | font_face: font_display: 'swap' }}
With swap, text is drawn in a system font and redrawn when the theme font arrives, which
reflows headings and paragraphs if the two fonts differ in size. You have three practical options:
- Change
font_displaytooptionalfor the body font, so a font that is not ready almost immediately is kept for the next page view rather than swapped in. - Preload the body font so it is more likely to arrive before first paint:
<link rel="preload" as="font" type="font/woff2" href="{{ settings.type_body_font | font_url }}" crossorigin>in the head, for one or two faces only. - Use a system font for body text. The theme font picker includes system options that need no download at all, which removes the problem entirely.
Each extra weight you enable, such as bold body text or a separate heading weight, is another file. Keep the number low. The web fonts guide explains how to tune a fallback font so the swap barely moves anything.
The fix, step by step
Step 1
Test key templates with and without apps
Duplicate the live theme and work only on the copy. Run the CLS Debugger on a home page, a busy collection and a product with several images and variants, and record throttled mobile loads in Chrome DevTools while scrolling. Then turn app embeds off one at a time in the theme editor and retest, noting which apps change the score. Search the theme code for snippets left behind by apps you have removed.
Step 2
Let Liquid output image dimensions
Search
the theme's sections and snippets for hand-written <img tags. Replace each with the image_url
and image_tag filters, which add width and height automatically,
or add width="{{ image.width }}" and height="{{ image.height }}" yourself.
Check custom sections, blog templates and any code pasted in by an agency, since built-in theme sections
are usually already correct in current themes, and preview the result on a phone.
Step 3
Fix product card and gallery ratios
In
the theme editor, set the product card image ratio to a fixed shape such as square or portrait rather
than adapting to each image, unless every product photo already shares one shape. On product pages, give
the main media frame a fixed aspect ratio in the theme CSS with object-fit: contain, so
switching image or variant never changes the gallery's height. Plan future product photography at one
consistent ratio.
Step 4
Reserve space for app blocks and payment buttons
For each app that shifts content, inspect its loaded widget on mobile and desktop
and note its height. Add a min-height for its container in the section's Custom CSS or the
theme stylesheet, including review stars on product cards and instalment messages. Do the same for the
accelerated checkout button container and payment terms. Where an app only offers an embed that injects
content unpredictably, ask the developer for an app block or a fixed-height option.
Step 5
Reserve space for sections loaded later
Identify sections that the theme fetches after load, such as product recommendations, complementary
products and recently viewed items, by scrolling a product page while watching the Network panel. Measure
each section once loaded and give its container a matching min-height at each breakpoint
in the theme CSS. Decide how products with no recommendations should behave, so the reserved space does
not close up while a shopper is reading.
Step 6
Tune the theme fonts
Open
layout/theme.liquid and find the font_face filters. Change font_display
for the body font to optional, add a preload link built with font_url for the
main body face, and switch off font weights and styles you do not really use in Typography settings.
If the brand allows, choose a system font for body text. Compare a cold, throttled load before and after
to confirm text no longer reflows.
Step 7
Publish and watch field data
When the duplicate theme tests clean in the CLS Debugger on every template, publish it, keeping the old theme unpublished as a fallback. Retest the live pages, since some apps behave differently on the published theme. Then watch the Core Web Vitals report in Search Console and the store's web performance reports over the next four weeks, and recheck after installing any new app or theme update.
Keeping the store stable
- Test every new app on a duplicate theme with the CLS Debugger before enabling it on the live theme.
- Record your custom CSS changes. Theme updates can overwrite edited files. Keep a note of each reserved height and where it lives, or keep them in a separate stylesheet you add back after updating.
- Agree an image standard for new products: one ratio and a minimum size, so cards and galleries stay uniform.
- Review app embeds occasionally and switch off any the store no longer uses.
Store-wide speed beyond layout shift is covered in Core Web Vitals on Shopify.
1. Why does my Shopify product page jump when it loads?
The most common causes are apps adding content after the page appears, such as review stars, instalment payment messages and upsell boxes, and the accelerated checkout buttons drawn by Shopify script. Below the product details, recommendation sections that are fetched as you scroll add more movement. A gallery that changes height between images is another. Test the page with app embeds switched off in a duplicate theme to see how much comes from apps.
2. Do Shopify apps cause layout shift?
Many can, because they inject their content with JavaScript after the theme has drawn the page. Whether that shifts the page depends on where the content goes and whether space was reserved for it. App blocks you place in the theme editor are easiest to fix, because you know where they are and can give their container a minimum height. Embeds and older script-based apps may need the developer to offer a fixed-height option.
3. Does the Dawn theme have layout shift problems?
Dawn and other current Shopify themes output images with width and height and generally handle their own sections well, so a fresh install usually has little shift. Problems tend to come from what is added: apps, custom sections, product photos of mixed shapes with the card ratio set to adapt, fonts added in custom code and recommendation sections without reserved height. Testing a clean duplicate theme next to your live one shows how much you have added.
4. Should I change font_display to optional on Shopify?
For body text it is usually the most stable choice. With optional, if the font is not ready almost immediately, the store keeps the system font for that page and uses the theme font on the next page once it is cached, so text never reflows mid-read. For a heading or logo font where the brand look matters more, keep swap and preload that font instead. Edit the font_face filter in theme.liquid on a duplicate theme first.
5. How do I reserve space for product recommendations on Shopify?
Load a product page, scroll to the recommendations and measure the loaded section height on a phone-sized screen and on desktop. Then add a min-height for the recommendations container to the theme CSS, using a media query for the mobile value. The fetched products then fill space that already exists instead of pushing the footer down. If some products have no recommendations, check that the empty space is removed before the shopper reaches it.
6. Will theme updates undo my CLS fixes?
They can. Updating a theme to a new version from the Theme Store replaces the theme files, and edits you made to those files may not carry over. Settings made in the theme editor, such as the card image ratio and section Custom CSS, are usually kept, but code edits in theme.liquid and asset files often need reapplying. Keep a written list of each change, with its file, and retest with the CLS Debugger after every update.
The CLS Debugger records every layout shift while a page loads, names the elements involved, adds up the cumulative score and ranks each shift by impact, so you can see whether theme images, apps or fonts are behind it.
Open CLS Debugger Fixes โ