Skip to main content

Stop Lazy Loading Product and Hero Images

By Jason Grigsby

Published on September 12th, 2023

Topics

I see a recurring performance problem on many ecommerce sites—the most important images on the page are being lazy loaded when they shouldn’t be. It impacts user experience, Core Web Vitals, and by extension search engine optimization. You’re better off not implementing lazy loading at all than implementing it incorrectly.

For example, a former client of ours asked us to look at the performance of their product page. They saw a significant drop in conversion after they launched a new design and wanted to know why.

Digging into the network waterfall chart, I noticed that the product image—the most important image on the page—took 16 seconds to appear on a fast 3G connection. It was the 60th asset downloaded.

I can’t talk about the client site in detail so I found another site, Mongoose Bikes, using the same Shopify theme. Let’s use their amazing, retro Supergoose bike as our example page. The video below captures how the page loads:

In this test, the main product photo takes 14.39 seconds to load on a simulated Fast 3G / Slow 4G connection. This network speed is what Google’s Lighthouse and Page Speed Insights tools uses to measure performance.

There’s a lot of performance issues here. The page takes 44 seconds to finish loading. But for now, let’s focus on the main product image which takes over 14 seconds to appear. It was the 69th asset downloaded.

This image is not only the most important image for customers, but it is also the image that will count towards the Largest Contentful Paint measure. Largest Contentful Paint is one of the Core Web Vitals that Google promotes and uses in its search engine page ranking algorithm.

If you’re not a technical person, don’t worry. I recorded a quick video showing you how to check if images on your site are lazy loading when they shouldn’t be.

A quick, non-technical guide to identifying lazy loaded images

One of the first things I do when investigating why an image is loading so late is to view the source of the page to find the code for the image. It is important to view the source of the page, not look at the current DOM in developer tools. We want to know what the browser can see when it downloads the initial HTML document. If we look at the DOM, we’re seeing what the browser knows after CSS and JavaScript have been processed.

This is the markup associated with the Supergoose’s main product image:

<img class="photo-zoom-link__initial lazyload"
     data-src="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_{width}x.png?v=1632622682"
     data-widths="[360, 540, 720, 900, 1080]"
     data-aspectratio="1.5"
     data-sizes="auto"
     alt="Supergoose">
<noscript>
  <img 
     class="photo-zoom-link__initial lazyloaded" 
     src="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_740x.png?v=1632622682" 
     alt="Supergoose">
</noscript>
Code language: HTML, XML (xml)

The page uses a JavaScript-based carousel to display multiple product images. There are four data attributes and an entire noscript block used to hide this image from the browser’s pre-loader so that the image isn’t download early and is instead lazy loaded.

But this is the main product image. It is the most important image on the page. This image shouldn’t be lazy loaded. If anything, we should consider preloading the image. It is that important.

The Supergoose page contains another mistake I commonly see coupled with lazy-loaded images. The main product photo has a one second fade-in animation applied to it.

Google’s Largest Content Paint guidance states:

To provide a good user experience, sites should strive to have Largest Contentful Paint of 2.5 seconds or less.

If you’re trying to pass Core Web Vitals to maintain your search ranking, you can’t afford to spend almost half of your LCP budget on animation.

It seems like many developers have heard that lazy loading images is good for performance. They want to build fast sites so they lazy load all of the images.

But when you lazy load all of the images, you defeat the purpose of lazy loading. The benefit of lazy loading is telling the browser to defer images and other assets that aren’t immediately needed.

For example, someone may never scroll down far enough on a page to see an image located in the footer. If that footer image starts downloading immediately, it may delay assets that are more important. So lazy loading says to the browser, don’t download this footer image until it is needed. Typically, this means don’t download it until the user scrolls the page far enough that it seems likely the image will be seen.

By contrast, images at the top of the page—logos in the header, hero and product images—are immediately visible. They need to load as quickly as possible so the page feels complete and ready to be interacted with.

Browsers are pretty smart about prioritizing assets in their download queue. They read an HTML document from top to bottom and assume that images found higher in the document are more important and start downloading them sooner.

That is unless you lazy load all of the images. In that case, you’re hiding images from the browser and substituting your own knowledge of the loading order over that of the browser. If you lazy load images above the fold, you’re shooting yourself in the performance foot.

It used to be that in order to lazy load images, you needed to implement a JavaScript library. Thankfully, this is no longer the case.

All evergreen browsers now support the loading attribute. You can specify loading="lazy" on an image and get most of the behavior you want without all of the Javascript overhead. Here’s how the earlier markup would look using this web standard.

<img class="photo-zoom-link__initial"
     src="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_540x.png?v=1632622682"
     srcset="//www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_360x.png?v=1632622682 360w, 
             //www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_540x.png?v=1632622682 540w,
             //www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_720x.png?v=1632622682 720w,
             //www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_900x.png?v=1632622682 900w,
             //www.mongoose.com/cdn/shop/products/M21_20U_Supergoose-BLK_PD_1080x.png?v=1632622682 1080w"
     sizes="auto"
     loading="lazy"
     alt="Supergoose">Code language: HTML, XML (xml)

There is a lot less code needed. There is no need to hide images in a noscript tag because the images are never hidden from the browser’s pre-loader.

As we’ve already discussed, this image shouldn’t be lazy loaded at all. So the ideal markup wouldn’t include the loading="lazy" attribute at all.

But one nice thing about using loading="lazy" is that if you accidentally apply it to an image high up on the page, the browser will load it as soon as it builds the page layout and realizes the image is in the initial viewport.

That’s still an unnecessary delay, but the image will still load earlier than if you’ve used a JavaScript library to implement lazy loading. If you use a JavaScript library, the browser won’t know to download the image until that JavaScript library executes and that is typically much later.

The one use case where it continues to make sense to use lazy loading JavaScript libraries is for images in menus, carousels, or other images that are hidden. You can see this happening on the Supergoose page.

The first two images the browser downloads are a US flag and world icon that are only seen on small screens if you open the menu and scroll to the bottom:

The US flag and globe icon at the bottom of the menu are the first images downloaded.

While these icons are small in file size, they are images that many users will never see. They shouldn’t download ahead of images that are immediately visible in the viewport.

But while these icons may not be a big deal, the next images downloaded are more problematic.

Five photos. The first is of three BMX riders on a dirt trail. The second is of a table of people signing posters. The third is of three pairs of shoes. The fourth is multiple people on bikes with a rail in a park where it appears they will be doing tricks. The final images says, "Dirtvana Mongoose."
Five photos that are among the next to download.

These images are nowhere to be found when viewing the website on a mobile phone. It is only when you open the menu on a wide screen that you discover where these images are being used:

A screenshot of the Supergoose page on a wide screen. The Universe menu item is selected opening up five menu options Legacy, Team, News, Jam and Dirtvana. Above each menu item are the mystery images from above.

On small screens, these images shouldn’t be downloaded at all. On wide screens, they shouldn’t download ahead of more important images like the main product photo. These are images that we should lazy load.

Unfortunately, using loading="lazy" won’t work for these images because it makes decisions on when to load images based on whether or not an image is in the current viewport. It doesn’t care if the images are hidden. In this case, the menu is in the initial viewport so the browser will begin downloading those images ahead of more important assets like your hero image.

This is where JavaScript libraries like lazysizes continue to shine. They can be used to ensure hidden images don’t download until they are in view. And because the images will never be viewed on small screens, they images will never be downloaded.

In short, use loading="lazy" for images that scrolling will bring into view. Use JavaScript libraries for hidden images that you want to lazy load.

  • It is better to not lazy load anything than to lazy load the wrong things.
  • The goal with lazy loading is to delay the loading of assets further down on the page that users may not scroll to. The closer something is to the top of the page, the less likely it is to be something you want to lazy load.
  • I know there is no fold because there is no consistent screen size, but in this case, we do care about the fold. We want items above the fold to load as quickly as possible. Instead of lazy loading, we may want to preload above the fold images.
  • Our Largest Contentful Paint budget is only 2.5 seconds. Don’t waste time on animating images.
  • Don’t use JavaScript libraries for lazy loading images (with a few exceptions). Instead use the new web standard of loading="lazy".

Lazy loading is a key technique when it comes to increasing page performance, but like any technique, it shouldn’t be applied everywhere.

Comments

Weston Ruter said:

Something else that is helpful to improve LCP is to add fetchpriority=high to your LCP image (e.g. the hero). WordPress 6.3 includes this among its image performance enhancements, in addition to reducing lazy-loading for initial images. Granted, this does not work for every site as we discussed on Mastodon, but we’re continuing to iterate.

Keith Wilcox said:

Great article! I like that it covered the most important and often overlooked strategy of thinking about your content first. In this case it’s a hero image, on another page it might be the video player. Youtube is great at this. The entire page (top nav, comments, next up, etc) lazy loads only after the video player is done. Once that point was established, it was great to touch on using the browser supported method first, and fancier JS methods second, but still with keeping the strategy in mind.