Never lazy-load the LCP image
Eagerly load images visible in the initial viewport. Never apply loading="lazy" or use a JavaScript-based lazy loader on the primary LCP target or other critical image in the initial viewport.
Lazy-loading the LCP image is one of the most common and damaging performance anti-patterns in Shopify themes. Server-side rendering with image_tag gives the browser maximum lead time on the LCP image, but only when the image is loaded eagerly.
When Liquid renders an img tag with a src attribute into the HTML, the browser's preload scanner discovers the image URL while parsing the HTML, before any CSS has been parsed, any JavaScript has run, or layout has begun. This gives the browser a head start on downloading the image as early as possible in the page load.
There are two related anti-patterns. First, loading="lazy" on the LCP candidate deprioritizes it and defers the download until after the browser processes a hidden IntersectionObserver event. Second, JavaScript lazy loading libraries like lazysizes replace the src attribute with a non-standard data-src, which hides the URL from the preload scanner entirely until JavaScript executes. Both practices are common, but they negate the benefits of the preload scanner and add latency.
Anchor to Remove ,[object Object], from the LCP imageRemove loading="lazy" from the LCP image
loading="lazy" from the LCP imageChange loading="lazy" to loading="eager", or remove the attribute. Eager loading is the default browser behavior.
Anchor to Remove JavaScript lazy loading librariesRemove Java Script lazy loading libraries
Libraries like lazysizes, lozad, and vanilla-lazyload replace src with data-src, hiding the image URL from the preload scanner until JavaScript executes.
To migrate:
- Replace
data-srcwithsrcon all images. - Add
loading="lazy"to images outside the initial viewport. - Remove the library script tag.
Anchor to Use ,[object Object], for conditional loadingUse section.index for conditional loading
section.index for conditional loadingThe image_tag filter automatically applies loading="eager" for sections 1 through 3, and loading="lazy" for sections 4 and later. It also applies loading="eager" whenever section.index0 is nil, which is the case in the online store editor, in static sections, and in the Section Rendering API. Don't set the loading attribute when relying on this default.
When you override the default with section.index, match that nil handling. Comparisons against nil are falsey in Liquid, so {% if section.index <= 3 %} sends the LCP image to the else branch in exactly the contexts where the built-in default keeps it eager. Test the lazy case as a positive section.index > 3 comparison instead, and let everything else, including nil, fall through to eager. For the full explanation, refer to Load above-the-fold and below-the-fold sections differently with section.index:
Anchor to Use ,[object Object], for image-dense sectionsUse forloop.index for image-dense sections
forloop.index for image-dense sectionsA collection section might render up to 50 products at a time, all within the first section. section.index alone isn't sufficient here, because only the first few images are visible in the viewport and are LCP candidates. Use forloop.index to enable eager loading for just those cards, and leave the rest to be lazy loaded:
Anchor to Avoid auto-sizes on LCP imagesAvoid auto-sizes on LCP images
Calculating sizes with JavaScript, as some lazy loading libraries do, delays image download because the browser can't select the correct source until after JavaScript runs. Always provide an explicit sizes attribute for images visible in the initial viewport:
Anchor to ExamplesExamples
Anchor to Example 2. Migrate from lazysizesExample 2. Migrate from lazysizes
Before, with lazysizes:
After, with image_tag. Remove the script tag from your layout file and provide an explicit sizes attribute, because you can no longer use data-sizes="auto":
Anchor to Example 3. Product card grid with ,[object Object]Example 3. Product card grid with forloop.index
forloop.indexAnchor to TestingTesting
- Use the Insights tab in the Performance panel to identify whether the LCP image is being lazy loaded.
- Use the Network panel to check when the LCP image request starts. It should appear early in the waterfall, discovered by the preload scanner, not after JavaScript executes.
- Compare Performance panel runs with
loading="lazy"andloading="eager"on the LCP image to see the LCP difference.
Anchor to ReferencesReferences
forloop.indexsection.indeximage_tagfilter- Lazy load images for performance
- How layout position impacts three big web performance levers
- Announcing new Liquid features for better web performance
- Use
section.indexfor position-aware loading - Mark the LCP image with
fetchpriority="high" - Prevent image layout shift
- Use responsive images