What Is Lazy Loading?
Lazy loading is a strategy that delays loading non-critical resources until they're actually needed. Instead of downloading every image on the page the moment a visitor arrives, the browser only loads images as the visitor scrolls toward them.
Think of it like a restaurant kitchen: instead of preparing every dish on the menu the moment the restaurant opens, they cook each dish when a customer orders it. The result? Faster first service and less wasted effort.
On a typical Shopify product or collection page, you might have 20–50 images. Without lazy loading, the browser tries to download all of them simultaneously, fighting for bandwidth and slowing down the images your visitor actually sees first — the hero banner, the product photo, the navigation icons.
With lazy loading, only the above-the-fold images load immediately. Everything below the fold waits until the visitor scrolls near it. The result: faster initial page render, lower bandwidth usage, and a better experience — especially on mobile.
How it works technically: The browser uses the IntersectionObserver API to watch when an element is about to enter the viewport. When it detects the image is close, it triggers the download. With native lazy loading (loading="lazy"), this is all handled by the browser — zero JavaScript required from your end.
Why Lazy Loading Matters for Shopify Stores
Shopify stores are image-heavy by nature. Product photos, lifestyle images, collection banners, trust badges, app widgets — a typical homepage can easily have 30+ images totaling 5–15 MB of data.
Without lazy loading, all of those images compete for bandwidth on initial page load. Here's what that means in practice:
Slower LCP (Largest Contentful Paint): The hero image — the most important visual element — loads slower because it's sharing bandwidth with 30 other images the visitor hasn't even scrolled to yet.
Higher bounce rates on mobile: Mobile connections are slower and more variable. Loading 10 MB of images on a 4G connection can take 8–12 seconds. If your store doesn't render fast, visitors bounce — and they don't come back.
Wasted bandwidth: Most visitors don't scroll to the bottom of the page. If 60% of your images are below the fold and 70% of visitors leave before scrolling, you're downloading megabytes of data that nobody sees.
Lower Core Web Vitals: Google uses Core Web Vitals as a ranking signal. Excessive image loading directly impacts LCP and can cause layout shifts (CLS) if image dimensions aren't set.
Proper lazy loading can cut initial page weight by 40–70% on image-heavy pages. That translates directly to faster first paint and better user experience. Lazy loading is just one piece of the puzzle though — for the full picture, read our complete optimization guide.
The #1 Lazy Loading Mistake (and It's Costing You 1 Second)
Here's the counterintuitive truth: the most common lazy loading mistake isn't failing to lazy load — it's lazy loading too aggressively.
Specifically, lazy loading your hero image or LCP element. According to Shopify's own performance team:
⚠️ The LCP Killer
"Shopify sites that lazy load their LCP image have a median LCP that is 1.0 second slower than the eager loaded ones."
— Shopify Performance Team, performance.shopify.com
Why does this happen? When you set loading="lazy" on an above-the-fold image, you add an unnecessary step:
Without lazy loading (correct):
Browser reads HTML → Finds image → Starts downloading immediately
With lazy loading on LCP (wrong):
Browser reads HTML → Finds image → Marks as lazy → Waits for IntersectionObserver → Detects image is in viewport → Then starts downloading
That extra detection step adds 200–1000ms of delay to your most important image. On mobile with slower JavaScript execution, it's even worse.
The rule is simple:
- ✓ Above the fold: Eager load (no
loadingattribute, orloading="eager") - ✓ Below the fold: Lazy load (
loading="lazy") - ✗ Never: Lazy load the hero image, product featured image, or any LCP candidate
Sounds simple, right? In practice, it's tricky. "Above the fold" is different on every device, every screen size, and every page template. What's above the fold on a 27" desktop monitor might be below the fold on an iPhone SE. Getting this right manually requires responsive awareness across dozens of breakpoints.
Skip the Manual Work: Thunder Handles Lazy Loading Automatically
Thunder Page Speed Optimizer applies intelligent loading strategies across your entire store. It automatically identifies your LCP image and eager-loads it while lazy loading everything below the fold — no code changes, no theme editing, no guessing about breakpoints.
But Thunder goes beyond basic lazy loading. It also:
- ✦ Defers render-blocking app scripts — the bigger speed killer that lazy loading alone can't fix
- ✦ Inlines critical CSS so your page renders instantly
- ✦ Preloads fonts to eliminate Flash of Invisible Text (FOIT)
- ✦ Monitors daily so optimizations stay effective as you add products and apps
Most stores see a 27+ point speed score improvement within minutes of installing. No developer needed. Check plans to find the right fit for your store.
Install Thunder — Free Trial →Already have Thunder installed? You can skip the manual implementation sections below — Thunder handles all of this automatically. Keep reading if you want to understand what's happening under the hood.
Native vs JavaScript Lazy Loading: Which to Use in 2026
There are two approaches to lazy loading images, and this decision matters more than most Shopify developers realize.
Native Lazy Loading (loading="lazy")
Added to the HTML spec in 2019, native lazy loading is now supported by 96%+ of browsers (all modern Chrome, Firefox, Safari, and Edge versions). You simply add loading="lazy" to your <img> or <iframe> tag:
<img
src="product-photo.jpg"
loading="lazy"
alt="Blue running shoes - side view"
width="800"
height="600"
/> That's it. No JavaScript library, no configuration, no bundle size increase. The browser handles everything.
JavaScript Lazy Loading (lazysizes, etc.)
Before native lazy loading, the standard approach was using JavaScript libraries like lazysizes. These libraries use the IntersectionObserver API to detect when images approach the viewport, then swap a placeholder data-src with the real src.
<!-- JS lazy loading pattern -->
<img
data-src="product-photo.jpg"
class="lazyload"
alt="Blue running shoes - side view"
/>
<script src="lazysizes.min.js" async></script> This still works but adds JavaScript overhead — typically 5–10 KB of additional script that needs to be downloaded, parsed, and executed before any lazy loading begins.
Comparison
Native (loading="lazy") | JavaScript (lazysizes) | |
|---|---|---|
| JS overhead | Zero | 5–10 KB |
| Browser support | 96%+ | 100% (polyfilled) |
| Setup complexity | One attribute | Library + class names |
| Threshold control | Browser decides | Customizable |
| SEO safety | Full (images in HTML) | Depends on implementation |
| Fallback | Eager loads (graceful) | Images may not load |
| Recommendation (2026) | ✓ Use this | Legacy — phase out |
Bottom line: In 2026, use native lazy loading. The 4% of browsers that don't support it will simply eager-load the images — which is perfectly fine. There's no reason to add a JavaScript library for this anymore. If your theme still uses lazysizes, it's time to migrate.
What to Lazy Load (and What NOT To)
Not all resources should be treated equally. Here's a practical breakdown for Shopify stores:
✓ Lazy Load These
Product images in collection grids — Below the first row, lazy load all thumbnails. A collection page with 48 products doesn't need all 48 images on initial load.
Below-fold sections — Testimonials, Instagram feeds, "You may also like" carousels, blog post previews — anything a visitor needs to scroll to see.
YouTube/Vimeo embeds — iframes are expensive. A single YouTube embed loads 500+ KB of scripts. Use loading="lazy" on iframes or replace with a static thumbnail that loads the iframe on click (facade pattern).
Additional product images — On product pages, the gallery images beyond the featured photo can be lazy loaded.
Footer content — Trust badges, payment icons, newsletter section images — all below fold on every page.
✗ Never Lazy Load These
Hero/banner images — This is almost always your LCP element. Lazy loading it costs ~1 second. Always eager load. Consider adding <link rel="preload"> for extra speed.
Product featured image — The main product photo on a PDP is the LCP candidate. Load it immediately.
Logo and navigation images — These are small, above the fold, and critical for first paint. Don't delay them.
Background images set via CSS — loading="lazy" only works on <img> and <iframe> tags. CSS background images require a different approach (IntersectionObserver to toggle a class).
Pro tip: Not sure what your LCP element is? Run your page through PageSpeed Insights and look for the "Largest Contentful Paint element" diagnostic. That's the image you must never lazy load.
How to Implement Lazy Loading in Shopify Liquid
If you're editing your theme code manually, here's how to implement lazy loading correctly in Shopify's Liquid templating language.
Method 1: Shopify's image_tag Filter (Recommended)
Shopify's Liquid image_tag filter automatically sets loading="lazy" on images. It also handles responsive srcset and sizes attributes for optimal delivery.
{{- product.featured_image | image_url: width: 800 | image_tag:
loading: 'lazy',
widths: '200,400,600,800',
sizes: '(min-width: 768px) 50vw, 100vw',
alt: product.featured_image.alt -}} For your hero/LCP image, explicitly set eager loading:
{{- section.settings.hero_image | image_url: width: 1200 | image_tag:
loading: 'eager',
fetchpriority: 'high',
widths: '600,900,1200,1800',
sizes: '100vw',
alt: section.settings.hero_alt -}} Key detail: Notice fetchpriority: 'high' on the hero image. This tells the browser to prioritize downloading this image over other resources. Combined with eager loading, it's the fastest way to render your LCP image. Learn more about LCP optimization on Shopify.
Method 2: Raw HTML with Loading Attribute
If you're writing raw HTML (custom sections, snippets), add the attribute directly:
<!-- Below-fold image: lazy load -->
<img
src="{{ image | image_url: width: 600 }}"
alt="{{ image.alt | escape }}"
width="{{ image.width }}"
height="{{ image.height }}"
loading="lazy"
decoding="async"
/>
<!-- Above-fold image: eager load -->
<img
src="{{ image | image_url: width: 1200 }}"
alt="{{ image.alt | escape }}"
width="{{ image.width }}"
height="{{ image.height }}"
loading="eager"
fetchpriority="high"
/> Always set width and height! Without explicit dimensions, lazy loaded images cause layout shift (CLS) when they load — the page jumps as the image pushes content down. Set width/height in the HTML or use CSS aspect-ratio to reserve space.
Method 3: Smart Loading Based on Section Position
Dawn and other Shopify 2.0 themes use a clever pattern: they track which section is first on the page and only eager-load images in that section.
{%- liquid
# In the layout or page template:
assign first_section = true
-%}
{%- for block in section.blocks -%}
{%- if first_section -%}
{%- assign loading_strategy = 'eager' -%}
{%- assign fetch_priority = 'high' -%}
{%- assign first_section = false -%}
{%- else -%}
{%- assign loading_strategy = 'lazy' -%}
{%- assign fetch_priority = 'auto' -%}
{%- endif -%}
{{- block.settings.image | image_url: width: 800 | image_tag:
loading: loading_strategy,
fetchpriority: fetch_priority -}}
{%- endfor -%} This approach is better than blanket lazy loading, but it's still imperfect — it doesn't account for how many images are visible above the fold in each section, and it varies by device.
The manual approach gets complex fast. Between responsive breakpoints, dynamic sections, and app-injected content, getting lazy loading right on every page template is hours of theme development — with ongoing maintenance every time you change your layout. This is exactly what Thunder automates. Try it free →
Beyond Images: Lazy Loading Videos, Iframes, and Sections
Images are the obvious target, but lazy loading principles apply to other heavy resources too.
Video Embeds (YouTube, Vimeo)
A YouTube embed loads 500–800 KB of JavaScript plus the video player UI — even if the visitor never hits play. Two common fixes:
Option A: Native lazy iframe
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
loading="lazy"
title="Video title"
allow="accelerometer; autoplay; encrypted-media; gyroscope"
allowfullscreen
></iframe> Option B: Facade pattern (better)
Show a static thumbnail with a play button. Only load the full iframe when clicked. This saves 500+ KB for visitors who never play the video.
<div class="video-facade" data-video-id="VIDEO_ID">
<img
src="https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg"
alt="Video thumbnail"
loading="lazy"
/>
<button class="play-btn" aria-label="Play video">▶</button>
</div> Shopify Section Rendering API
Shopify introduced the Section Rendering API which lets you load entire sections on demand — not just images. This is like lazy loading on steroids: instead of loading just the image later, you load the entire section's HTML, CSS, and JavaScript only when needed.
Combined with IntersectionObserver, you can build a true "load on scroll" experience where below-fold sections don't exist in the initial HTML at all. This is particularly powerful for:
- • Product recommendation sections
- • Review widgets
- • Instagram feed sections
- • Heavy third-party app widgets
Third-Party App Scripts
This is where the real performance wins hide. Most Shopify apps inject render-blocking scripts into your store's <head>. While you can't directly "lazy load" a script with the loading attribute, you can defer or async them:
<!-- Blocking (bad) -->
<script src="app-widget.js"></script>
<!-- Deferred (better) -->
<script src="app-widget.js" defer></script>
<!-- Async (good for independent scripts) -->
<script src="app-widget.js" async></script> The problem? You can't always control how apps inject their scripts. Many apps hardcode synchronous script tags that you can't modify without breaking the app. This is the single biggest reason why manual optimization hits a ceiling — and why Thunder exists. Thunder intercepts and re-sequences all script loading automatically, regardless of how apps inject them.
How to Test If Lazy Loading Is Working
You've added loading="lazy" to your images — but how do you know it's actually working? Here are four ways to verify:
1. Chrome DevTools Network Tab
The most reliable method:
- Open your store in Chrome → Right-click → Inspect → Network tab
- Filter by Img (image type)
- Hard refresh the page (
Cmd+Shift+R/Ctrl+Shift+R) - Note how many images load initially (this is your eager-loaded count)
- Now scroll down slowly and watch new image requests appear
If all images load on initial page load regardless of scroll position, lazy loading isn't working.
2. Inspect Element
Right-click any below-fold image → Inspect. Look at the <img> tag in the Elements panel. You should see loading="lazy" as an attribute.
3. PageSpeed Insights Audit
Run your page through PageSpeed Insights or test your store speed with our free tool. If lazy loading isn't implemented, you'll see the suggestion: "Defer offscreen images" with an estimate of potential savings (in KB and seconds).
4. Lighthouse in DevTools
Chrome DevTools has a built-in Lighthouse tab. Run a performance audit and check for two diagnostics:
- • "Defer offscreen images" — lazy loading not implemented or incomplete
- • "Largest Contentful Paint image was lazily loaded" — you're lazy loading your LCP (fix this immediately!)
Watch for: The Lighthouse warning "Largest Contentful Paint image was lazily loaded" is the most critical diagnostic. If you see this, your LCP image has loading="lazy" and it's costing you ~1 second. Switch it to loading="eager" with fetchpriority="high" immediately.
Thunder vs Manual Lazy Loading
| ⚡ Thunder | Manual Implementation | |
|---|---|---|
| Setup time | 60 seconds | 2–6 hours |
| Skill needed | None | Liquid + HTML + JS |
| LCP detection | Automatic (per page) | Manual per template |
| App script optimization | Included | Not possible manually |
| Responsive handling | All breakpoints | Best guess |
| Ongoing maintenance | Zero (automatic) | Every theme/layout change |
| Risk of breaking store | Zero (reversible) | Medium (code edits) |
| Typical improvement | 27+ speed score points | 5–15 points (images only) |
Frequently Asked Questions
Does Shopify automatically lazy load images?
Partially. Shopify's Dawn theme and most modern themes set loading='lazy' on images below the fold using the Liquid image_tag filter. However, this only works if the theme developer implemented it correctly. Many older themes and custom themes don't lazy load at all — or worse, lazy load everything including the hero image, which hurts LCP. Thunder Page Speed Optimizer handles this automatically, applying intelligent lazy loading to below-fold images while ensuring above-fold images load eagerly.
Can lazy loading hurt my Shopify store's performance?
Yes — if applied incorrectly. Lazy loading your hero image or LCP element adds an unnecessary delay because the browser waits for JavaScript to detect the image is in the viewport before loading it. Shopify's own performance team found that stores lazy loading their LCP image have a median LCP 1.0 second slower than those that eager-load it. The rule is simple: eager-load above the fold, lazy-load below. Thunder applies this rule automatically.
What's the difference between native lazy loading and JavaScript lazy loading?
Native lazy loading uses the browser's built-in loading='lazy' attribute — no extra JavaScript required. JavaScript lazy loading uses libraries like lazysizes with the IntersectionObserver API. Native lazy loading is now recommended because it's lighter (zero JS overhead), supported by 96%+ of browsers, and handles graceful degradation automatically. The only advantage JS-based lazy loading still has is more granular control over loading thresholds, which rarely matters for most Shopify stores.
Should I lazy load Shopify app scripts?
Absolutely. Third-party app scripts are one of the biggest speed killers on Shopify. While you can't lazy load scripts the same way as images, you can defer them — loading them after the critical content renders. This is technically script deferral rather than lazy loading, but the concept is the same: delay non-critical resources. Thunder does this automatically for all app scripts, which is why most stores see 20-30+ point speed score improvements.
How do I check if lazy loading is working on my Shopify store?
Open your store in Chrome, right-click an image below the fold, and select Inspect. Look for loading='lazy' in the img tag. You can also open Chrome DevTools → Network tab → filter by 'Img' → scroll down the page and watch new image requests appear as you scroll. If all images load at once on page load, lazy loading isn't working. For a comprehensive audit, run your store through PageSpeed Insights and check for the 'Defer offscreen images' suggestion.
Does lazy loading affect SEO on Shopify?
When implemented correctly, lazy loading has zero negative SEO impact. Googlebot renders JavaScript and can see lazy-loaded images. The key is ensuring your images have proper alt text and are in the HTML (not dynamically injected). Native lazy loading with loading='lazy' is fully supported by Google's crawler. The SEO risk comes from broken implementations where images are hidden behind JavaScript that Googlebot can't execute — but this isn't a concern with native lazy loading or Thunder's implementation.
Related Resources
Shopify Image Optimization Guide
Formats, compression, responsive images, and more
Shopify LCP Optimization
The complete guide to Largest Contentful Paint
Render-Blocking Resources on Shopify
How to identify and fix blocking CSS and JS
Core Web Vitals Guide
LCP, CLS, INP — what they mean and how to fix them