Introduction
On Shopify storefronts, images often make up 60–80% of the total payload. From hero banners and product galleries to blog headers and collection grids, every extra image request adds latency, delays the Largest Contentful Paint (LCP), and increases total blocking time. Lazy‑loading defers the download of non‑critical images until they approach the viewport, dramatically speeding up initial page rendering and conserving mobile data. This end‑to‑end guide shows you how to integrate native lazy‑loading into your Liquid templates—complete with responsive srcset, aspect‑ratio placeholders, conditional eager loading, and accessibility best practices—so your store feels instantaneous without sacrificing visual richness.
1. Performance Context
| Metric / Concept | Why It Matters |
|---|---|
| Largest Contentful Paint (LCP) | Slow image downloads inflate LCP, hurting user perception and SEO. |
| Total Payload Size | Images dominate bytes transferred, especially on mobile. |
| Data Savings | Mobile users save bandwidth by only downloading images they view. |
Key Principle:

Eager‑load only above‑the‑fold (hero, logo, first row of products).
Lazy‑load everything else.
2. Native vs. Script‑Based Lazy‑Loading
2.1 Native Browser Support (Recommended)
Add loading="lazy" to <img>:
liquidCopyEdit<img
src="{{ image | image_url: width: 600 }}"
alt="{{ image.alt | escape }}"
loading="lazy"
>
Pros: No JS required; built‑in browser heuristics.
Cons: Very old browsers (<2020) lack support, but share is negligible.
2.2 Fetch Priority for Critical Images
For your hero or logo—critical for LCP—use:
liquidCopyEdit<img
src="{{ section.settings.hero_image | image_url: width: 1600 }}"
alt="{{ section.settings.hero_image.alt | escape }}"
loading="eager"
fetchpriority="high"
>
loading="eager"or omit (default).fetchpriority="high"hint for supporting browsers.
2.3 IntersectionObserver Fallback (Optional)
For legacy support or advanced blur‑up patterns:
htmlCopyEdit<img
data-src="full.jpg"
src="placeholder.jpg"
class="lazyload blur-up"
alt="…">
<script>
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver((entries, obs) => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
const img = entry.target;
img.src = img.dataset.src;
img.onload = () => img.classList.add('loaded');
obs.unobserve(img);
});
}, { rootMargin: '200px' });
document.querySelectorAll('img.lazyload').forEach(img => io.observe(img));
}
</script>
3. Preparing Responsive Images in Liquid
3.1 Resizing via image_url
Shopify’s CDN resizes on the fly:
liquidCopyEdit{%- assign img = product.featured_image -%}
<img
src="{{ img | image_url: width: 800 }}"
alt="{{ img.alt | escape }}"
width="{{ img.width }}"
height="{{ img.height }}"
loading="lazy"
>
- Intrinsic width/height attributes reserve layout space and prevent CLS.
3.2 Responsive srcset & sizes

Let the browser choose the optimal variant:
liquidCopyEdit{%- assign img = product.featured_image -%}
<img
src="{{ img | image_url: width: 600 }}"
srcset="
{{ img | image_url: width: 300 }} 300w,
{{ img | image_url: width: 600 }} 600w,
{{ img | image_url: width: 900 }} 900w,
{{ img | image_url: width: 1200 }} 1200w
"
sizes="(max-width: 600px) 100vw, 600px"
alt="{{ img.alt | escape }}"
loading="lazy"
>
- sizes: maps viewport widths to image display size.
4. Conditional Lazy‑Loading in Liquid
4.1 Eager First Row in a Product Grid
Load only the first N images eagerly:
liquidCopyEdit<ul class="product-grid">
{%- for product in collection.products -%}
{%- assign img = product.featured_image -%}
<li>
<img
src="{{ img | image_url: width: 400 }}"
alt="{{ product.title | escape }}"
width="{{ img.width }}"
height="{{ img.height }}"
{% if forloop.index <= 4 %}
loading="eager" fetchpriority="high"
{% else %}
loading="lazy"
{% endif %}
>
</li>
{%- endfor -%}
</ul>
4.2 Merchant Toggle via Theme Setting
Expose a toggle in schema:
jsoncCopyEdit{
"type": "checkbox",
"id": "lazy_images",
"label": "Enable lazy-loading images"
}
Use in Liquid:
liquidCopyEdit{% assign lazy_attr = section.settings.lazy_images | default: true | ternary: 'lazy', 'eager' %}
<img
src="{{ img | image_url: width: 800 }}"
alt="{{ img.alt | escape }}"
loading="{{ lazy_attr }}"
>
5. Preventing Layout Shifts
5.1 Intrinsic Dimensions
Always output width and height:
liquidCopyEdit<img
src="{{ img | image_url: width: 600 }}"
width="{{ img.width }}"
height="{{ img.height }}"
loading="lazy"
>
5.2 Aspect‑Ratio Box
For background or decorative images:
liquidCopyEdit{%- assign ratio = img.aspect_ratio | default: 1.0 -%}
<div style="position:relative;padding-top:{{ 100.0 | divided_by: ratio }}%;">
<img
src="{{ img | image_url: width: 600 }}"
alt="{{ img.alt | escape }}"
loading="lazy"
style="position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;"
>
</div>
6. Low‑Quality Placeholder & Blur‑Up
Enhance perceived performance with a tiny blurred preview:
6.1 Generate Placeholder
liquidCopyEdit{% assign placeholder = img | image_url: width: 20 %}
{% assign main_src = img | image_url: width: 800 %}
<div class="blur-up">
<img
src="{{ placeholder }}"
data-src="{{ main_src }}"
alt="{{ img.alt | escape }}"
class="blur-up__img lazyload"
>
</div>
6.2 CSS Transition
cssCopyEdit.blur-up__img {
filter: blur(20px);
transition: filter 400ms;
}
.blur-up__img.loaded {
filter: blur(0);
}
Use JS from §2.3 to swap data-src and add .loaded.
7. Lazy‑Loading Background Images

For modules using background-image:
liquidCopyEdit<div
class="banner banner--lazy"
data-bg="{{ section.settings.bg_image | image_url: width: 1920 }}"
role="img"
aria-label="{{ section.settings.bg_image.alt | escape }}"
></div>
jsCopyEditconst io = new IntersectionObserver((entries, obs) => {
entries.forEach(({ isIntersecting, target }) => {
if (!isIntersecting) return;
target.style.backgroundImage = `url("${target.dataset.bg}")`;
obs.unobserve(target);
});
}, { rootMargin: '300px' });
document.querySelectorAll('.banner--lazy').forEach(el => io.observe(el));
Provide a light CSS background to avoid flash.
8. Accessibility & SEO
altText: Always meaningful or empty (alt="") for decorative images.- Semantics: Use
<img>for content; for decorative, use CSS backgrounds witharia-hidden="true". - No layout jumps: Pre‑define dimensions or aspect ratios.
9. Testing & Measurement
| Test Type | Tools / Techniques |
|---|---|
| Lab (Lighthouse) | Compare LCP, Total Bytes, Requests before/after. |
| RUM | Capture LCP and transferSize via Performance API. |
| Coverage | Chrome DevTools > Coverage to verify critical vs deferred. |
| Scroll Simulation | Throttled (3G) network, scroll test for lazy loads. |
10. Reusable lazy-image Snippet
Create snippets/lazy-image.liquid:
liquidCopyEdit{%- assign img = include.image -%}
{%- assign w = include.max_width | default: img.width -%}
{%- assign eager = include.eager | default: false -%}
<img
src="{{ img | image_url: width: w }}"
srcset="
{{ img | image_url: width: w }} {{ w }}w,
{{ img | image_url: width: w | times: 2 }} {{ w | times: 2 }}w
"
sizes="{{ include.sizes | default: '100vw' }}"
alt="{{ img.alt | escape }}"
width="{{ img.width }}"
height="{{ img.height }}"
loading="{{ eager | ternary: 'eager', 'lazy' }}"
{% if eager %} fetchpriority="high" {% endif %}
class="{{ include.class | default: '' }}"
>
Use anywhere:
liquidCopyEdit{% render 'lazy-image',
image: product.featured_image,
max_width: 800,
eager: forloop.index <= 4,
class: 'product-img',
sizes: '(max-width: 600px) 100vw, 400px' %}
Conclusion
By eager‑loading only critical above‑the‑fold images and lazy‑loading the rest:

- LCP improves as hero assets arrive unblocked.
- Bandwidth is conserved for mobile users.
- Perceived speed climbs with faster initial paint.
Start by enabling loading="lazy" on grid thumbnails below the first row. Measure the impact, then expand to carousels, blog posts, and background modules—using the reusable snippet above. Within a few iterations, your Shopify store will feel snappier, rank higher on performance metrics, and turn more browsers into buyers.























































































































































































































































































































































































































































































































































































































































































