Shopify Lazy Loading: LCP and INP Fixes That Work

Artjola Zalla

Written by Artjola Zalla

Front End Dev at Shero Commerce

Shopify Lazy Loading

Lazy loading is the most misapplied fix in Shopify performance work. In most stores we audit, removing it improves LCP more than adding it. Developers either pile lazy loading on top of what the theme already does, or apply it to the hero image, making LCP worse.

The fix is not more lazy loading. It is precision. Load the right things first, defer the rest.

 This article shows exactly which images and scripts to target, the Liquid patterns from a real Shero theme, and how to verify the result without breaking anything.

What Shopify already lazy loads (and what it doesn’t)

Modern Shopify themes built on Online Store 2.0, including Dawn, Craft, and Sense, apply the loading=“lazy” attribute to images by default, using the browser’s native lazy-loading feature. There is no plugin, no JavaScript library, and no extra configuration required. When a merchant installs one of these themes out of the box, below-the-fold images are already deferred.

What this means in practice: if you are working on a standard OS 2.0 theme and you add loading=“lazy” to images that do not already have it, you are likely duplicating work that is already done. 


The fold line is the dividing rule. Everything above it needs loading="eager" and fetchpriority="high" on the LCP element. Everything below it is safe to defer with loading="lazy

Where the gaps are

The gap is conditional logic. Dawn and most OS 2.0 themes apply loading=“lazy” as a default, but they do not always detect whether a given section is above or below the fold. 

A merchant can drag the image banner section into the first position in the theme editor, and that section may still carry loading=“lazy” on its image because the theme does not know it is now the first thing on the page.

The result is that the most important image on the page, the one that determines your LCP score, gets deferred.

The LCP trap: why lazy loading can hurt your score

Before making any changes, you need to know which element the browser is treating as your Largest Contentful Paint. Changing the wrong image makes no difference. Changing the right one can move your LCP score by several seconds.

How to identify your LCP element

Open Chrome, navigate to your store in an incognito window, and open DevTools. Go to the Performance tab, click the record button, reload the page, and stop the recording. 

In the Insights panel on the left, you will see LCP with a time value and a breakdown showing Time to first byte and Element render delay.

 

Shopify Lazy Loading

The LCP breakdown shows time to first byte at 40ms and element render delay at 234ms. If your element render delay is high, the image is likely being deferred when it should be loading eagerly.

Alternatively, run a Lighthouse audit directly in DevTools by clicking the Lighthouse tab and generating a report.

Lighthouse audit in DevTools showing a performance score of 70 and LCP at 3.9s. An LCP this high on a text-based hero site is almost always a deferred-image problem. That is a site where lazy loading hurts more than it helps.

What to lazy load and what not to

Not every image on a Shopify store should be treated the same way. The decision comes down to one question.

Is this element visible when the page first loads? If yes, load it eagerly. If no, defer it. 

The table below maps the most common elements to the appropriate strategy, based on Shopify’s best practices for lazy loading.


Element

Load Strategy

Reason

Hero/banner image (first section)

eager + fetchpriority="high"

Almost always, the LCP element is on the homepage and landing pages

Second hero slide or block

lazy

Not visible on initial load

Blog card images

lazy

Always below the fold on listing pages

Product card images (collection grid)

lazy

Banner above the grid owns LCP in most cases

First product card (no banner above grid)

eager

May own LCP if nothing above it

Logo in header

eager

Visible on every page load, but keep small and optimized

Nav/menu images

eager

Visible on load, typically lightweight

Review widget images

lazy

Never rendered above fold

Footer images

lazy

Never above fold

For a broader look at how lazy loading fits into your overall image strategy, see our Shopify image optimization guide

The fix: eager loading and fetchpriority=“high” on the hero

Once you have confirmed which element is your LCP, the fix is two attributes on the image tag: loading=“eager” and fetchpriority=“high”. 

Loading eager tells the browser not to defer this image. Fetchpriority high tells the browser to download it before other resources on the page. Used together on the LCP image, these two attributes are the single highest-impact change you can make to LCP.

There are two ways to implement this, depending on how the section is built. 

  • If the image is rendered using a raw HTML img tag, add the attributes directly.

  •  If it uses Shopify’s Liquid image_tag filter, pass them as parameters to the filter. Both approaches produce the same output HTML.

The loading attribute below is set conditionally using an eager_block_id variable: if the block ID matches the designated eager block, loading is set to eager; otherwise, it falls back to lazy. 

 

The highlighted line 60 shows loading=“{% if block.id == eager_block_id %}eager{% else %}lazy{% endif %}”. This is a valid approach, but it requires the developer to manually designate which block gets eager loading.

Now let’s take a more solid approach. The conditional has been simplified to forloop.first: if this is the first block in the loop, apply loading=“eager” and fetchpriority=“high”; otherwise, apply loading=“lazy”.

cards-shero-hero.liquid with forloop.first conditional on line 43. The first image in the block loop gets loading=“eager” and fetchpriority=“high”; all subsequent images get loading=“lazy”.

Using section.index to conditionally apply fetchpriority

The forloop.first approach works well within a section that renders multiple blocks. But there is a more precise method for sections that render a single hero image: using section.index to detect whether the current section is the first section on the page.

Section.index is a Liquid variable that returns the position of the current section in the template. If section.index equals 1, this section is first. That means its image is almost certainly above the fold, regardless of which template it appears in or how the merchant has arranged sections in the theme editor.

The screenshot below shows this pattern in hero.liquid. The section assigns fetch_priority = ‘auto’ as the default, then overrides it to ‘high’ if section.index == 1. The fetch_priority variable is then passed to the image tag further down in the template. This is the cleanest and most portable implementation: it works correctly whether the hero section is first, second, or third on any given page.

 

hero.liquid lines 110-114. fetch_priority defaults to ‘auto’ and is overridden to ‘high’ only when section.index == 1. This ensures fetchpriority is applied precisely to the first section on the page without hardcoding it to a specific section name.

How to lazy load images below the fold in Liquid

Once you have protected the LCP image from lazy loading, you can be confident about applying loading=“lazy” to everything else. The correct pattern is to set the attribute directly on the img tag for any image that is not visible on page load.

Notice that the full context is shown, not just the attribute in isolation. The image tag includes src, srcset with multiple widths, sizes for responsive breakpoints, alt text, width, and height. The loading attribute sits alongside these, not instead of them. A complete, well-formed image tag with lazy loading is what you are aiming for.

 

_blog-post-card.liquid showing loading="lazy" on both the article image (line 69) and the placeholder fallback (line 81).

The above approach is correct because blog card images are always below the fold on a listing page: they appear in a grid that the user scrolls to, not at the top of the viewport. 

Applying it in a product card loop

Product card images require slightly more thought than blog images because the first row of cards on a collection page is visible on page load. Lazy loading the first row defers images that the browser would have loaded immediately anyway, which can cause a flash of empty space as the user arrives on the page.

The correct approach is to lazy load all product images by default, but use additional logic to handle the first visible items differently if needed. 

 card-gallery.liquid lines 109 to 122. The loading variable is assigned ‘lazy’ at line 111 as the default for all product media in the loop. Variant image logic and slide count limiting follow below.

In practice, the first row of product cards on a collection page rarely constitutes the LCP element (typically the banner), so lazy-loading them is acceptable. If PSI flags the collection page product images as a problem, the fix is to apply the forloop.first pattern here too, setting eager on the first iteration and lazy on the rest.

Lazy loading and INP: it’s about scripts, not images

You've protected the LCP image and deferred everything below the fold. That handles the visual load. But if your store still feels sluggish after the page renders, the problem has shifted from images to scripts.

INP measures the latency of every interaction throughout the page session: clicks, taps, keyboard input. On most Shopify stores, the number is worse than it needs to be, and images are not the reason.

What causes poor INP on Shopify stores

The primary cause of poor INP on Shopify stores is not images. It is third-party scripts running synchronously on the main thread. 

Our Speed benchmarks across 1,000 Shopify stores found INP is the second biggest drag on the composite score.

When the browser is executing JavaScript, it cannot respond to user interactions. Every millisecond spent running a chat widget, review app, tracking pixel, or analytics script is a millisecond the page cannot react to a button press or menu tap.

The most common offenders are Google Tag Manager loaded synchronously, live chat scripts (Gorgias, Intercom, Tidio), loyalty and rewards apps, and heavy review widgets like Yotpo or Stamped. None of these need to be available the instant the page loads. They can all be deferred without any meaningful impact on the user experience.

On this Shopify store, the GTM tag added 430.9 KiB of network payload for a Lighthouse Performance score of 91.

The table below shows some of the causes/fixes we notice on the Shopify store.


If Your Store Has

The Fix

Expected Improvement

Klaviyo, Facebook Pixel, or Google Analytics

Defer scripts

100-200ms

Product variants (sizes, colors)

Optimize variant selector

150-300ms

Review apps (Judge.me, Loox, Yotpo)

Lazy load reviews

50-100ms

Collection pages with 48+ products

Lazy load images

100-200ms

Mega menu with lots of links

Load menu on hover

80-150ms


Read the full guide on Shopify INP Optimization to find the exact fix for your store

Defer, async, and load-on-interaction patterns

There are three ways to stop a third-party script from blocking the main thread. 

  • The async attribute tells the browser to download the script in parallel and execute it as soon as it is ready, without waiting for HTML parsing to finish.

  •  The defer attribute does the same, but delays execution until after the HTML is fully parsed and preserves the order of execution if multiple deferred scripts are present. For most third-party app scripts, defer is the safer choice.

  • The load-on-interaction is the most aggressive option. The script does not load at all until the user performs a qualifying action such as scrolling, moving the mouse, touching the screen, or pressing a key. This can dramatically reduce initial page weight and improve both LCP and INP, at the cost of a brief delay before the widget is ready.

In theme.liquid, Google Tag Manager is loaded synchronously: a script element is created, the GTM URL is assigned to its src, and it is immediately appended to the document head. This blocks HTML parsing until GTM has downloaded and executed.

theme.liquid lines 9 to 17. GTM is loaded synchronously. A script element is created and immediately appended to the document head on line 15, blocking the main thread while GTM downloads and initializes.

theme.liquid lines 9 to 41. GTM loads only after the first user interaction, with a requestIdleCallback fallback at 3,000ms.

How to test without breaking your theme

Run PageSpeed Insights at pagespeed.web.dev and enter your store URL. Run the mobile test first: mobile scores are typically lower and represent a more realistic worst-case scenario for your customers.

 

PageSpeed Insights mobile is showing a performance score of 50, LCP at 8.0s, and Total Blocking Time at 480ms. LCP this high on a Shopify store is almost always caused by a lazily loaded hero image or an unoptimized LCP element.

 The second screenshot shows the same site after applying the fixes: converting the hero image to eager loading with fetchpriority=“high”, and deferring third-party scripts. 

 

After applying the fixes, PageSpeed Insights mobile is showing a performance score of 78, an LCP of 4.0s, and a Total Blocking Time of 230ms. The hero image change and script deferral together account for the majority of this improvement.

The one check you must do every time

After any change to image-loading attributes, rerun the Performance tab recording in Chrome DevTools and confirm that the LCP element is not lazy-loaded. This is the single most important verification step. One missed loading=“lazy” on the hero image will undo all other work.

Do not rely solely on PSI scores for this check. PSI uses a simulated test environment and scores can vary between runs. DevTools gives you direct visibility into what the browser is actually doing during a real page load on your machine.

Common mistakes to avoid

Keep a checklist of these before marking any performance work as complete.

  • Lazy loading the hero or banner image. Remove loading=“lazy” from any image that is visible on page load. Add loading=“eager” and fetchpriority=“high”. Verify in DevTools after every change.

  • Dawn’s image banner section carrying loading=“lazy” when placed first. In sections/image-banner.liquid, check for loading=“lazy” and add section.index == 1 logic to override it to eager when the section is first.

  • Applying fetchpriority=“high” to multiple images. The browser ignores the hint when everything is marked high priority. Use fetchpriority=“high” on one element per page only, and let the browser prioritize everything else naturally.

  • Using a third-party lazy load JS library on top of native browser lazy loading. Modern browsers handle this natively. Adding a library like lazysizes creates conflict risk, adds script weight, and solves a problem that does not exist on OS 2.0 themes.

  • Not verifying the LCP element after changes. Run DevTools after every image attribute change. PSI scores are a lagging indicator. DevTools shows you what is actually happening on the page right now.

Conclusion and next steps

Getting lazy loading right is less about knowing the attributes and more about knowing where to apply them. The same applies to all your Shopify performance optimization efforts.

If your Shopify store is slow, we can tell you exactly why.

Our experts audit theme performance at the code level: LCP element identification, image loading logic, script deferral, and Core Web Vitals across your key page templates.  No generic recommendations, no automated reports.

Book a performance audit here

F.A.Qs on Shopify Lazy Loading

Does Shopify lazy loading affect SEO and Google rankings?

Shopify lazy loading does affect SEO, and done correctly it helps your rankings rather than hurts them. Google's crawler renders pages and accesses image alt text even when loading is delayed, so lazy-loaded images are indexed correctly. The real SEO gain comes from speed: improving your Core Web Vitals through lazy loading gives your store a better shot at ranking higher in search results.

The danger runs the other way. If you apply lazy loading to your hero image, you are delaying the Largest Contentful Paint element, which Google uses as a direct ranking signal. Do that and lazy loading actively hurts your SEO. Protect the LCP image with loading="eager" and fetchpriority="high", defer everything below the fold, and lazy loading becomes a ranking asset rather than a liability.

Can Shopify lazy loading break my apps or third-party widgets?

It can, and it is one of the more common things merchants report after a performance update. The tell is non-functioning widgets or features that stop working after lazy loading is enabled. The cause is almost always timing: an app script initializes before the element it is targeting has loaded into the viewport.

The fix is a deferred script loader that waits for the main page content to be ready before app scripts execute, ensuring dependencies are available before initialization runs. If a review widget stops rendering or a chat bubble disappears after making lazy loading changes, that is where to look. The lazy loading itself is not the problem. The app is initializing against an element that does not exist yet.

I have lazy loading set up on my Shopify store, but it does not seem to be working. What should I check?

There are four distinct reasons Shopify lazy loading stops working, and each has a different fix. Check these in order:

  • The loading attribute only works on <img> and <iframe> tags, not on CSS background-image properties. If your hero section renders its image as a CSS background, the attribute does nothing. Convert it to a proper <img> tag in Liquid.
  • A hardcoded attribute is blocking Shopify's automatic logic. Shopify's image_tag filter will not override loading attributes you have already set manually. Search your theme files for hardcoded loading= values and replace them with the section.index conditional pattern.
  • A JavaScript lazy loading library is conflicting with native browser lazy loading. If your theme uses a legacy data-src and IntersectionObserver setup alongside loading="lazy", the two will fight each other. Remove the JS library. The browser handles this natively now.
  • Images are missing explicit width and height attributes. Without dimensions, the browser does not reserve space and may behave inconsistently. Shopify's image_tag filter sets these automatically -- raw <img> tags written by hand often do not.
What does a Shopify performance audit from Shero Commerce actually include?

A Shero Commerce performance audit covers the full picture, not just a PageSpeed Insights score. It starts with a page-by-page CWV analysis across your homepage, collection pages, and product pages using both lab data and real user data from the Chrome UX Report.

From there, the audit identifies your LCP element for each page type, checks whether it loads correctly, flags any above-the-fold images with loading="lazy", and maps every third-party script that blocks your main thread.

You get a prioritized list of fixes ranked by impact, the Liquid and JavaScript changes needed to implement them, and a before-and-after PSI comparison once the work is done. If your theme has accumulated performance debt from app installs, custom code, or developer changes, the audit surfaces exactly where it lives.

Contact us to book an audit.

How is a Shero Commerce performance audit different from running PageSpeed Insights myself?

PageSpeed Insights tells you your store is slow. It does not tell you why, where, or what to do about it in any way a non-developer can act on.

A Shero Commerce performance audit takes that diagnosis and does the work. That means opening the actual Liquid files in your theme, finding the exact image tag that needs fetchpriority="high", identifying which third-party script in theme.liquid is blocking your main thread and rewriting how it loads, and locating every section file where loading="lazy" is quietly hurting your LCP instead of helping it.

Every store is different. A review app on one store costs 400ms. A synchronous GTM implementation on another costs a full second. A hero image with the wrong loading attribute on a third is the entire reason the LCP is failing. The audit identifies specific problems in your theme and fixes them at the code level.

Learn more here.

Artjola Zalla

Front End Dev at Shero Commerce

Artjola Zalla is a Shopify developer at Shero Commerce specializing in theme development, front-end performance, and custom Liquid implementations. She works directly in the code that powers client storefronts, tackling everything from Core Web Vitals optimization to complex theme architecture challenges. Artjola brings a precise, client-focused approach to every build, turning performance problems into solved problems.