Fix Shopify Cumulative Layout Shift (CLS) | Blue Frog Docs

Fix Shopify Cumulative Layout Shift (CLS)

Shopify-specific solutions for CLS issues including product images, dynamic content, fonts, and app-related layout shifts.

Fix Shopify Cumulative Layout Shift (CLS)

Cumulative Layout Shift (CLS) measures visual stability. Layout shifts frustrate users and hurt conversion rates, especially on product and collection pages.

Target: CLS under 0.1 Good: Under 0.1 | Needs Improvement: 0.1-0.25 | Poor: Over 0.25

For general CLS concepts, see the global CLS guide.

Shopify-Specific CLS Issues

1. Product Images Without Dimensions

The most common CLS issue on Shopify is product images loading without reserved space.

Problem: Images load and push content down as they appear.

Diagnosis:

  • Run PageSpeed Insights
  • Look for "Image elements do not have explicit width and height"
  • Watch product/collection pages load and observe shifts

Solutions:

A. Add Width and Height Attributes

Always specify image dimensions:

<!-- Before: No dimensions -->
<img src="{{ product.featured_image | image_url: width: 800 }}" alt="{{ product.title }}">

<!-- After: With dimensions -->
<img
  src="{{ product.featured_image | image_url: width: 800 }}"
  width="{{ product.featured_image.width }}"
  height="{{ product.featured_image.height }}"
  alt="{{ product.title }}"
>

For responsive images, use aspect ratio:

<img
  src="{{ product.featured_image | image_url: width: 800 }}"
  width="800"
  height="{{ 800 | divided_by: product.featured_image.aspect_ratio | round }}"
  alt="{{ product.title }}"
  loading="lazy"
>

B. Use Aspect Ratio CSS (Modern Approach)

<div class="product-image" style="aspect-ratio: {{ product.featured_image.aspect_ratio }};">
  <img
    src="{{ product.featured_image | image_url: width: 800 }}"
    alt="{{ product.title }}"
    loading="lazy"
  >
</div>
.product-image {
  width: 100%;
  overflow: hidden;
}

.product-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

C. Dawn Theme Product Images

In Dawn theme, edit sections/main-product.liquid:

{%- for media in product.media -%}
  <img
    srcset="{{ media | image_url: width: 375 }} 375w,
            {{ media | image_url: width: 750 }} 750w,
            {{ media | image_url: width: 1100 }} 1100w"
    src="{{ media | image_url: width: 1100 }}"
    alt="{{ media.alt | escape }}"
    width="{{ media.width }}"
    height="{{ media.height }}"
    loading="lazy"
  >
{%- endfor -%}

2. Collection Grid Images

Collection pages often have multiple images loading simultaneously.

Solution: Reserve Space for Grid Items

<!-- Collection product grid -->
<div class="product-grid">
  {% for product in collection.products %}
    <div class="product-card">
      <div class="product-image-wrapper" style="aspect-ratio: 1;">
        <img
          src="{{ product.featured_image | image_url: width: 400 }}"
          width="400"
          height="400"
          alt="{{ product.title }}"
          loading="lazy"
        >
      </div>
      <h3 class="product-title">{{ product.title }}</h3>
      <p class="product-price">{{ product.price | money }}</p>
    </div>
  {% endfor %}
</div>
.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
}

.product-image-wrapper {
  width: 100%;
  position: relative;
  overflow: hidden;
}

.product-image-wrapper img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

3. Font Loading Layout Shifts

Custom fonts can cause layout shifts as they load.

Problem: Text reflows when custom font replaces fallback font.

Solutions:

A. Use font-display: swap

@font-face {
  font-family: 'CustomFont';
  src: url('{{ 'custom-font.woff2' | asset_url }}') format('woff2');
  font-display: swap; /* Prevents invisible text, may cause shift */
  font-weight: 400;
  font-style: normal;
}

B. Use font-display: optional (Better for CLS)

@font-face {
  font-family: 'CustomFont';
  src: url('{{ 'custom-font.woff2' | asset_url }}') format('woff2');
  font-display: optional; /* Prevents layout shift, may use fallback */
  font-weight: 400;
}

C. Match Fallback Font Metrics

Use a fallback font that closely matches your custom font's metrics:

body {
  font-family: 'CustomFont', Arial, sans-serif;
  /* Adjust fallback to match custom font */
  font-size-adjust: 0.5;
}

D. Preload Fonts

In theme.liquid <head>:

<link
  rel="preload"
  href="{{ 'custom-font.woff2' | asset_url }}"
  as="font"
  type="font/woff2"
  crossorigin
>

E. Use Shopify Font Picker

Shopify's native font picker automatically optimizes font loading:

  • Theme EditorTheme SettingsTypography
  • Fonts are automatically served from Shopify CDN
  • Better optimized than custom fonts

4. Shopify App-Induced Layout Shifts

Apps are a major cause of CLS on Shopify stores.

Common Culprits:

A. Trust Badges / Social Proof Apps

  • "X people viewing this product"
  • "Recently purchased" notifications
  • Review stars appearing late

B. Popup / Email Capture Apps

  • Popups pushing content down
  • Slide-in banners
  • Announcement bars appearing late

C. Wishlist / Compare Apps

  • Buttons added to product cards after load
  • Icons appearing above product images

D. Live Chat Widgets

  • Chat bubble shifting footer
  • Widget expanding unexpectedly

Solutions:

A. Reserve Space for App Elements

If you must use apps that inject content:

/* Reserve space for trust badge */
.product-trust-badge {
  min-height: 40px; /* Reserve space */
}

/* Reserve space for review stars */
.product-reviews {
  min-height: 24px;
}

B. Load Apps After Page Load

Delay app scripts to prevent shifts:

document.addEventListener('DOMContentLoaded', function() {
  setTimeout(function() {
    // Load app script here
    const script = document.createElement('script');
    script.src = 'https://app-domain.com/widget.js';
    document.body.appendChild(script);
  }, 1000); // Delay 1 second
});

C. Replace Apps with Native Features

Instead of apps:

  • Use Shopify's native product reviews (Settings → Apps)
  • Use static trust badges (add to theme, not via app)
  • Use GTM for analytics (not individual app pixels)

D. Audit and Remove Problematic Apps

  1. Test CLS with apps disabled:

    • Temporarily disable apps one by one
    • Test CLS after each
    • Identify biggest offenders
  2. Find alternatives:

    • Look for lighter apps
    • Use native Shopify features
    • Implement features in theme code

5. Dynamic Content and Injected Elements

Content that appears after page load causes shifts.

Common Issues:

A. Cart Drawer Opening

  • Cart sliding in from side
  • Pushes content or overlays unexpectedly

Solution: Use CSS transforms (don't shift layout)

.cart-drawer {
  position: fixed;
  top: 0;
  right: 0;
  transform: translateX(100%); /* Off-screen */
  transition: transform 0.3s ease;
  z-index: 1000;
}

.cart-drawer.open {
  transform: translateX(0); /* Slide in without shifting layout */
}

B. Announcement Bars

  • Appearing after page load
  • Pushing header down

Solution: Reserve space or use fixed positioning

.announcement-bar {
  position: sticky;
  top: 0;
  min-height: 40px; /* Reserve space even when empty */
  z-index: 100;
}

C. Dynamic Pricing / Inventory

  • "Only X left" messages
  • Price changes (sales, discounts)

Solution: Reserve space with min-height

<div class="product-price" style="min-height: 28px;">
  {{ product.price | money }}
</div>

<div class="inventory-message" style="min-height: 20px;">
  {% if product.inventory_quantity < 10 %}
    Only {{ product.inventory_quantity }} left!
  {% endif %}
</div>

6. Shopify Section Rendering

Shopify sections can cause CLS if not structured properly.

Issue: Sections Loading Sequentially

Sections appear one by one, each pushing content down.

Solution: Optimize Section Rendering

A. Use Placeholder Heights

<!-- In section schema settings -->
{
  "name": "Featured Collection",
  "settings": [
    {
      "type": "range",
      "id": "section_height",
      "min": 300,
      "max": 800,
      "step": 50,
      "default": 500,
      "label": "Section height"
    }
  ]
}
<!-- In section liquid -->
<div class="featured-collection" style="min-height: {{ section.settings.section_height }}px;">
  <!-- Content -->
</div>

B. Critical CSS for Above-Fold Sections

Inline CSS for first sections in <head>:

<style>
  .hero-section {
    min-height: 600px;
    width: 100%;
  }
</style>

7. Product Variant Changes

Switching variants can cause shifts if not handled properly.

Issue: Product Image Changes Size

Different variants have different image sizes.

Solution: Fixed Aspect Ratio Container

<div class="product-images">
  {% for image in product.images %}
    <div class="product-image-container" style="aspect-ratio: 1;">
      <img
        src="{{ image | image_url: width: 800 }}"
        alt="{{ image.alt }}"
        data-variant-image="{{ image.variant_ids | join: ',' }}"
        loading="lazy"
      >
    </div>
  {% endfor %}
</div>
.product-image-container {
  width: 100%;
  position: relative;
  overflow: hidden;
}

.product-image-container img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

8. Mobile-Specific CLS Issues

Mobile devices often experience worse CLS due to slower connections and smaller viewports.

Common Mobile Issues:

A. Mobile Navigation Menu

  • Menu appearing late
  • Hamburger icon shifting

Solution: Reserve space for mobile header

@media (max-width: 768px) {
  .header {
    min-height: 60px; /* Reserve mobile header space */
  }

  .mobile-menu-toggle {
    width: 44px; /* Explicit size for tap target */
    height: 44px;
  }
}

B. Mobile Product Images

  • Larger shifts on small screens
  • Zoom features causing shifts

Solution: Fixed aspect ratio especially important on mobile

<div class="product-image-mobile" style="aspect-ratio: {{ product.featured_image.aspect_ratio }};">
  <img
    src="{{ product.featured_image | image_url: width: 750 }}"
    alt="{{ product.title }}"
  >
</div>

C. Mobile Sticky Elements

  • Sticky "Add to Cart" bar
  • Sticky header shrinking/expanding

Solution: Use fixed positioning, not sticky (for critical elements)

.mobile-add-to-cart {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 60px; /* Fixed height */
  z-index: 100;
}

Testing CLS

Tools

1. PageSpeed Insights

  • Lab data (simulated)
  • Field data (real users)
  • Identifies specific shifting elements

2. Chrome DevTools

  • More ToolsRenderingLayout Shift Regions
  • Highlights shifting elements in real-time
  • Visual debugging

3. Web Vitals Extension

  • Chrome Extension
  • Shows CLS score in real-time
  • Helps identify when shifts occur

4. WebPageTest

  • Filmstrip view shows shifts frame-by-frame
  • Compare before/after optimizations

Manual Testing Process

  1. Open Chrome DevTools (F12)
  2. Enable "Layout Shift Regions":
    • More Tools → Rendering → Check "Layout Shift Regions"
  3. Reload page and watch for blue highlights
  4. Test interactions:
    • Scroll down page
    • Switch product variants
    • Add to cart
    • Open cart drawer
  5. Note which elements shift
  6. Fix highest-impact shifts first

Test Multiple Scenarios

  • Fresh page load (clear cache)
  • Slow 3G connection (throttle in DevTools)
  • Mobile devices (real device testing)
  • Different product types (with/without multiple images)
  • Empty vs full cart

Quick Wins Checklist

  • Add width and height to all product images
  • Add width and height to collection grid images
  • Use aspect-ratio CSS for image containers
  • Set font-display: optional for custom fonts
  • Reserve space for app-injected elements
  • Fix announcement bar positioning
  • Optimize cart drawer to not shift layout
  • Use fixed heights for dynamic content areas
  • Test with Layout Shift Regions enabled
  • Uninstall/replace problematic apps

Shopify Theme-Specific Issues

Dawn Theme

  • Generally good CLS
  • May need image dimension fixes in custom sections
  • Check third-party app integrations

Legacy Themes (Debut, Brooklyn, etc.)

  • Often missing image dimensions
  • Older CSS may not use aspect-ratio
  • Recommendation: Update to Dawn or OS 2.0 theme

Custom Themes

  • Audit thoroughly
  • Ensure all images have dimensions
  • Test all interactive elements (cart, navigation, modals)

Common CLS Patterns in Shopify

Page Type Common CLS Source Fix Priority
Homepage Hero image, announcement bar Highest
Collection Product grid images High
Product Product images, reviews app Highest
Cart Cart drawer, dynamic totals Medium
Checkout Shopify-controlled (limited fixes) Low

When to Hire a Developer

Consider hiring a Shopify Expert if:

  • CLS consistently over 0.25 after basic optimizations
  • Custom theme has complex layout issues
  • Apps deeply integrated into theme
  • Need to rebuild sections without shifts
  • Require advanced CSS/JavaScript optimization

Find Shopify Experts: shopify.com/partners

Monitoring CLS Over Time

Chrome User Experience Report (CrUX):

  • Real user CLS data
  • Available in PageSpeed Insights
  • 28-day rolling average

Google Search Console:

Continuous Monitoring:

  • SpeedCurve
  • Calibre
  • DebugBear
  • Set up alerts for CLS regressions

Next Steps

For general CLS optimization strategies, see CLS Optimization Guide.

// SYS.FOOTER