Frontend Fundamentals - Improve Core Web Vitals with Lazy Loading

Smartly use image lazy loading to improve core web vitals such as LCP and CLS

View Solution on Patreon
Frontend Fundamentals - Improve Core Web Vitals with Lazy Loading

Premise

An indispensable part of front end development is performance, and one way to measure it are Core Web Vitals. Contrary to other benchmarks, they try to capture the real-world user experience when browsing a website.

As such, this challenge is a bit of an outlier, as it doesn not deal with techniques reserved for Hotwire. It’s important know-how for full stack engineers nonetheless, that’s why we’ll cover the impacts of

  • Largest Contentful Paint (LCP)
  • Cumulative Layout Shift (CLS)

in this exercise.

Starting Point

We start with a classic web publication layout containing a hero image and a bunch of cards representing articles, with a feature image and title each. To explore the idiosyncrasies of lazy loading I encourage you to also try this out in a mobile (portrait) layout.

To improve loading times, the recommended practice is to lazy load offscreen images but avoid this for the LCP: https://web.dev/articles/optimize-vitals-lighthouse#lazy_loading_offscreen_images_and_avoiding_this_for_lcp.

What does this mean? We want to add loading="lazy" to every <img> element but refrain from doing this for everything that is visible on the page upon load, especially our hero image. Now, there are certain strings attached to this:

  • only adding loading="lazy" on turbo:load or DOMContentLoaded doesn’t cut it: it’s too late, images have already been loaded. The loading attribute has to be rendered server-side, which is why it’s already added for each <img> node.
  • the tricky part is to detect which images are already visible on page load before any fetching occurs. That’s why width and height attributes are added to each <img> element, in order to remove any layout shift.
  • this is exacerbated by the fact that browsers apply different viewport thresholds, i.e. different interpretations of what is “off screen” e.g. Chrome: https://web.dev/articles/browser-level-image-lazy-loading#distance-from-viewport. This not only varies between browsers, but also between devices and resolutions - hence the hint to try it in mobile mode above.

Challenge

The first challenge is to detect any image elements above the fold and remove the loading attribute. This is one of the rare cases where you’ll actually have to use an inline <script> tag in your HTML, because it’s evaluated and run before the fetching of other resources begins. I’ve tried many options, and the best dynamic one I’ve come up with (aside from defining a deliberate CSS class to find the heaviest images) is to filter images for those with a certain size (try a width of 640), and remove the loading attribute there:

The second improvement you can try is preconnecting to the domain serving the images (in our case picsum.photos), or even preloading the LCP image.

https://stackblitz.com/edit/core-web-vitals?file=index.html%3AL14

Scrolling through a browser showing the devtools' network tab

Caveat

Running a Lighthouse Audit on Stackblitz doesn’t really work and/or produces skewed results because of the embedded engine. So I encourage you to copy/paste the markup and try it out locally, in a container of your choice.

Cookies
essential