How to Diagnose Shopify Lazy Loading Issues
Before jumping into fixes, confirm that lazy loading is actually broken. Here's a quick 2-minute diagnostic:
Step 1: Check the Network Tab
- 1. Open your Shopify store in Chrome
- 2. Open DevTools (F12 or Cmd+Opt+I on Mac)
- 3. Go to the Network tab
- 4. Filter by Img (images only)
- 5. Hard reload: Ctrl+Shift+R (or Cmd+Shift+R)
- 6. Count the image requests that fire immediately
- 7. Now slowly scroll down — do new requests appear?
If all images load at once on page load: Lazy loading is not working at all.
If some images load on scroll but many load upfront: Lazy loading is partially working but some images are missed.
If images load progressively as you scroll: Lazy loading is working — the PageSpeed suggestion may be about a different issue.
Step 2: Inspect Individual Images
Right-click a below-fold image → Inspect. Check the <img> tag for:
loading="lazy" → Native lazy loading is set
class="lazyload" or data-src="..." → JavaScript lazy loading (may work but adds overhead)
No loading attribute → Image eager-loads by default
You can also run a quick check with our free Shopify speed test — it flags deferred image issues automatically.
Skip the Troubleshooting: Thunder Fixes All 8 Issues Automatically
Each fix below requires theme code editing, developer time, and ongoing maintenance. Thunder Page Speed Optimizer handles lazy loading, script deferral, critical CSS, and font optimization in one install — regardless of your theme or app configuration.
- ✦ Works with any theme — old, new, custom, or broken lazy loading
- ✦ Handles app-injected content — defers scripts and optimizes resources apps add
- ✦ Auto-detects LCP — never lazy loads what should be eager
- ✦ 27+ point average speed score improvement
Fix 1: Your Theme Doesn't Use image_tag
The problem: Many Shopify themes — especially those built before 2022 or heavily customized ones — use raw <img> tags instead of Shopify's image_tag Liquid filter. Without image_tag, there's no automatic lazy loading, no responsive srcset, and no WebP delivery.
How to check: Open your theme code (Online Store → Themes → Edit code) and search for image_tag in section files. If you find mostly raw <img src= tags instead, your theme isn't using Shopify's modern image pipeline.
The fix: Replace raw <img> tags with the image_tag filter. This requires editing Liquid files in your theme:
Before:
<img src="{{ image | image_url: width: 800 }}" alt="{{ image.alt }}" /> After:
{{ image | image_url: width: 800 | image_tag: loading: "lazy", alt: image.alt }}
For the full native lazy loading implementation guide, including section.index logic, see our Shopify native lazy loading guide.
Fix 2: App-Injected Images Bypass Your Theme's Lazy Loading
The problem: Every Shopify app that adds visual elements — review widgets (Judge.me, Loox), trust badges (Trust Hero), upsell popups (ReConvert), chat widgets (Tidio) — injects its own HTML and images directly into the page. These images are not rendered through your theme's Liquid templates, so your theme's lazy loading logic never touches them.
The average Shopify store has 6–8 apps. Each one might inject 1–5 images plus JavaScript. That's potentially 30+ images and scripts loading eagerly that your theme can't control.
How to check: In DevTools, look at below-fold images that loaded immediately. Right-click → Inspect. If the image is wrapped in a <div> with an app-specific class (like jdgm-widget or loox-widget) rather than in your theme's section HTML, it's app-injected.
The fix: This is the hardest issue to solve manually. Options:
Contact each app developer and ask them to add loading="lazy" to their injected images. Some will, most won't prioritize it.
Write custom JavaScript that finds app-injected images after page load and adds the lazy attribute. This is fragile and breaks when apps update their code.
Use a speed optimization app that handles app scripts at the network level, deferring entire app payloads until needed. This is what Thunder does automatically.
Fix 3: JavaScript and Native Lazy Loading Are Conflicting
The problem: Some themes include a JavaScript lazy loading library (like lazysizes) and native loading="lazy" attributes. This creates double processing: the browser's native IntersectionObserver fires, and the JS library runs its own observer. The result can be images that flash, load twice, or — in some cases — never load at all.
This also happens when merchants install a lazy loading app on top of a theme that already has native lazy loading.
How to check: Look for both of these on the same image:
- •
loading="lazy"(native) - •
class="lazyload"ordata-src(JavaScript library)
If you see both, there's a conflict. Also check for lazysizes.js or similar libraries in your theme's assets folder.
The fix: Remove the JavaScript lazy loading library and rely on native loading="lazy" instead. In 2026, native lazy loading has 96%+ browser support — there's no reason to add a JS library. Remove the library script from your theme and convert any data-src attributes back to src with loading="lazy".
Before (JS lazy loading):
<img data-src="product.jpg" class="lazyload" alt="Product" /> After (native lazy loading):
<img src="product.jpg" loading="lazy" alt="Product" width="600" height="400" /> Fix 4: CSS Background Images Can't Be Lazy Loaded
The problem: The loading="lazy" attribute only works on <img> and <iframe> elements. Images set via CSS background-image are always downloaded as soon as the CSS is parsed — even if the element is far below the fold.
Many themes use CSS background images for hero sections, banner overlays, parallax effects, and decorative patterns. These are invisible to native lazy loading.
How to check: In DevTools, find images in the Network tab that loaded immediately but aren't visible on screen. Click the image URL → check if it's referenced in a CSS file rather than an HTML <img> tag.
The fix: Two approaches:
Option A: Convert to <img> tags
Replace CSS background images with actual <img> elements positioned with CSS (using object-fit: cover and absolute positioning). This lets you add loading="lazy".
Option B: JavaScript IntersectionObserver
Write custom JS that watches for below-fold sections entering the viewport, then dynamically applies the background-image CSS. More complex but preserves your existing layout structure.
For above-fold hero sections, CSS background images are fine — they should load immediately. The issue is with below-fold sections using background images. Learn more about optimizing all types of images in our Shopify image optimization guide.
Fix 5: Custom Sections Missing the loading Attribute
The problem: Your theme's default sections might handle lazy loading correctly, but custom sections — added by developers or section-building apps — often skip it. A single custom section with eager-loaded images can load 5–10 extra images on every page.
How to check: Inspect images in different sections of your page. Pay attention to sections that look different from the theme's default style — these are likely custom additions. Check each one for loading="lazy".
The fix: Edit each custom section file and add the section.index logic:
{%- liquid
if section.index > 2
assign loading = "lazy"
else
assign loading = "eager"
endif
-%}
{{ section.settings.image | image_url: width: 1200 | image_tag: loading: loading }}
Apply this to every custom section that displays images. For an overview of how section.index works and when to use lazy vs eager loading, see our comparison guide.
Fix 6: Your Theme Lazy Loads Above-Fold Images (Hurting LCP)
The problem: This is the opposite problem — and ironically, it's worse than not having lazy loading at all. Some themes apply loading="lazy" to every image, including the hero banner and main product photo. This forces the browser to run the IntersectionObserver before downloading your most important image, adding 200–1000ms to your LCP.
⚠️ This Costs You 1 Second of LCP
Shopify's performance team measured a 1.0 second median LCP penalty for stores that lazy load their LCP image. Your hero image should always eager-load.
How to check: Go to your homepage. Right-click your hero/banner image → Inspect. If you see loading="lazy", it's hurting your LCP. Do the same for the main product image on a product page.
The fix: Change above-fold images to eager loading and add fetchpriority="high" to your LCP image:
{{ section.settings.hero_image | image_url: width: 1400 | image_tag: loading: "eager", fetchpriority: "high" }} For detailed LCP troubleshooting, see our Shopify LCP optimization guide.
Fix 7: Missing Width and Height Cause Layout Shift
The problem: Lazy loaded images without explicit dimensions cause Cumulative Layout Shift (CLS). Before the image loads, the browser reserves zero space for it. When the image finally downloads, the page jumps as it pushes content down. Visitors see alt text in a tiny collapsed space, then a sudden visual shift — a terrible experience.
How to check: In DevTools, watch for layout jumps as you scroll. You can also run a Lighthouse audit — CLS issues will be flagged with "Image elements do not have explicit width and height."
The fix: Always include width and height attributes on images, or use CSS aspect-ratio:
Option A: HTML attributes
<img src="product.jpg" loading="lazy" alt="Product" width="600" height="400" /> Option B: CSS aspect-ratio
.product-image {
aspect-ratio: 3 / 2;
width: 100%;
object-fit: cover;
} Pro tip: Shopify's image_tag filter adds width and height attributes automatically. If you're using raw <img> tags, you need to add them yourself. This is one more reason to use Shopify's native image_tag filter.
Fix 8: Safari and Browser Compatibility
The problem: Safari added native lazy loading support in version 15.4 (March 2022). If you're testing in an older Safari version, lazy loading won't work. More commonly, Safari's lazy loading threshold is different from Chrome's — Safari starts loading lazy images earlier (further from the viewport), which can make it seem like lazy loading isn't working on Apple devices.
How to check: If lazy loading works in Chrome but not Safari, check the Safari version. Also note that Safari's IntersectionObserver implementation has slightly different root margins — images may start loading 2–3 screen heights before they enter the viewport.
The fix: In most cases, Safari's behavior is actually fine — it's just more aggressive about preloading. If you need exact parity across browsers, you'd need JavaScript-based lazy loading with explicit thresholds, but native lazy loading with browser-default thresholds is the recommended approach in 2026.
For the truly paranoid, check caniuse.com/loading-lazy-attr for current browser support — it's at 96%+ globally, covering virtually all Shopify shoppers.
When to Stop DIY Troubleshooting
If you've got 2–3 of these issues happening simultaneously (which is common on stores with older themes and multiple apps), fixing them manually becomes impractical. Each fix requires theme code changes that can break during theme updates, and app-injected content is essentially outside your control.
This is where a dedicated speed optimization app earns its keep. Instead of patching individual issues, you get a solution that handles the entire optimization stack — images, scripts, CSS, fonts — across any theme and any combination of apps.
Not ready for an app? At minimum, run a free speed test to understand your current baseline and identify which issues are actually impacting your performance the most. For broader optimization guidance, our complete Shopify speed optimization guide covers every technique in detail.
Frequently Asked Questions
How do I know if lazy loading is working on my Shopify store?
Open Chrome DevTools (F12) → Network tab → filter by 'Img'. Hard reload the page. If all images load at once during page load, lazy loading isn't working. If new image requests only appear as you scroll down, lazy loading is active. You can also right-click any below-fold image → Inspect and look for loading='lazy' in the HTML.
Why does PageSpeed Insights still say 'Defer offscreen images' if I have lazy loading?
This usually means lazy loading isn't applied to all below-fold images. Common causes: your theme only lazy loads some images but misses others, app-injected images bypass theme lazy loading, or CSS background images can't be lazy loaded with the loading attribute. It can also trigger if the browser's lazy loading threshold loads images slightly before they enter the viewport.
Can Shopify apps break my theme's lazy loading?
Yes. Apps inject their own HTML, images, and scripts directly into the DOM, bypassing your theme's Liquid templates entirely. These app-injected images won't have loading='lazy' unless the app developer specifically added it. Additionally, some apps include their own lazy loading JavaScript that can conflict with native browser lazy loading, causing double-processing or images that never load.
Should I use a lazy loading app or fix it in my theme code?
If the issue is minor (a few images missing the loading attribute), fixing it in theme code is straightforward. If you have multiple apps injecting images, a heavily customized theme, or want comprehensive optimization beyond just image lazy loading (script deferral, critical CSS, font preloading), an app like Thunder Page Speed Optimizer handles everything automatically and works regardless of theme or app configuration.
Does lazy loading work on Shopify mobile?
Yes, native lazy loading (loading='lazy') works on all modern mobile browsers including Safari on iOS 15.4+, Chrome on Android, and Samsung Internet. If lazy loading appears broken only on mobile, check for: JavaScript lazy loading libraries that conflict with mobile browsers, images rendered at different sizes on mobile that might fall within the browser's pre-loading threshold, or mobile-specific theme templates that don't include the loading attribute.
How do I fix eager loading on my Shopify theme.liquid hero banner image?
If your hero banner image in theme.liquid loads with loading='lazy' instead of loading='eager', it's hurting your LCP by up to 1 second. Find the hero image tag in your theme's header or hero section file (not theme.liquid directly — check sections/header.liquid or sections/image-banner.liquid). Change loading='lazy' to loading='eager' and add fetchpriority='high'. If using image_tag, use section.index logic: sections with index 1 or 2 should eager-load, everything below should lazy-load. Thunder Page Speed Optimizer detects your LCP image automatically and never lazy loads it, regardless of your theme configuration.
Why are my lazy loaded images showing alt text before loading?
This happens when lazy loaded images don't have explicit width and height attributes (or CSS dimensions). Without dimensions, the browser can't reserve space for the image, so it shows the alt text in a collapsed space. When the image finally loads, the page jumps (layout shift). Fix this by ensuring every lazy loaded image has width and height attributes, or use Shopify's image_tag filter which adds dimensions automatically.
Get Your Shopify Lazy Loading Working Properly
Lazy loading issues on Shopify almost always come down to theme implementation gaps, app conflicts, or incorrect above-fold behavior. The fixes range from simple (adding loading="lazy" to a few images) to complex (overhauling custom sections and managing app-injected content).
Remember: even perfect lazy loading only addresses image delivery. Render-blocking scripts and unoptimized CSS often cause more damage than undeferred images. A comprehensive approach — handling images, scripts, CSS, and fonts together — delivers the best results.
Start here: Test your store's speed to see exactly what's impacting performance, then work through the fixes above — or let Thunder handle it automatically. Plans start at $19.99/month with a free trial.