How to Optimize LCP and Speed Index for Next.js Websites

Reduced 75% of image payload, optimized LCP by 62% and Speed Index by 24% of Next.js powered e-commerce website

Arijit Mondal
NE Digital
9 min readJan 7, 2021

--

Photo by Luke Chesser on Unsplash

At NE Digital, we are continuously looking for opportunities to improve the user’s online shopping experience. If we can optimize Largest Contentful Paint(LCP) and Speed Index that will help users to load images faster. Faster image loading irrespective of the internet connection is a key aspect to improve the overall user experience.

There are a couple of user-centric performance metrics that capture different moments during the lifecycle of the page. For improving the image loading experience we are going to focus on LCP(Largest Contentful Paint) and Speed Index metrics in this article.

How to identify the LCP element of a page

If we want to improve LCP then first we have to find out the largest contentful paint element.

The Largest Contentful Paint (LCP) metric reports the render time of the largest image or text block visible within the viewport. — Web Dev

Using lighthouse we can find the LCP element very easily as shown in the below image. We can see that lighthouse detects the banner image as the LCP element for our homepage.

how to find LCP element using lighthouse

How to check the Speed Index of a page

Speed Index measures how quickly content is visible during page load. We use the WebPageTest Filmstrip view extensively to understand the visual progression of the content of a page and investigate any possible delay in the appearance of any element.

Filmstrip view of WebpageTest

We also observed that the visual progress graph(present on the filmstrip view page of WebPageTest) to be very helpful in understanding how the content gets populated from a numeric viewpoint.

Visual Progress view of WebpageTest

WebPageTest has a detailed document that I highly recommend to understand how the Speed index is calculated and how different factors impact it.

Let’s find out how to optimize LCP and Speed Index

image credit: giphy

Now that we know how to check the Speed Index and find the LCP element, let’s see some of the strategies we took to improve them and reduce the overall image payload.

1) Don’t lazy load in-viewport images

For our website, we had built a generic image component on top of lazySizes to lazyload all of our images. Since we had many images on our homepage and PLPs(product listing pages), this strategy initially helped us to reduce the image payload as well as the overall payload size of the page.

The problem with js based image lazyloading approach is that images will not load until the js code is executed. So this was delaying loading of in-viewport image and as a result, increased LCP and Speed Index value. The same problem was highlighted by Harry Roberts in one of his tweets.

Tweet by Harry Roberts regarding lazy-loading in-viewport images

So we decided to use just <img> HTML tag for all of our above-the-fold images and use the generic lazy-loading image component for the below-the-fold images. We measured before/after results from WebPageTest, and it can be seen from the image below that both LCP and Speed Index have seen a huge improvement.

Before/After comparison from WebPageTest of removing lazyload from in-viewport images

Once we moved the changes to production, we noticed similar results from our Pagespeed dashboard.

improvement on LCP (left desktop and right mobile web)

After making this change we have seen significant improvement in LCP numbers. For mobile web, LCP dropped from 15.5 seconds → 4.5 seconds, and for desktop, it reduced from 4.1 seconds → 2.1 seconds

Bonus tip

Few of the browsers now support native lazy loading via <img loading=lazy> syntax. From the Caniuse data below we can see that currently, around 69% of Fairprice user’s browsers support it. Lazysizes library has a plugin that uses native lazyloading for supported browsers and uses js based solution for the unsupported ones.

loading lazy support for browsers

2) Preload LCP image

Preload can help with the early discovery of important resources. From the below image we can see that preload asks the browser to load the image upfront even before the browser actually discovers the image element post the js code execution.

With/without image preload, image credit lighthouse

Using next/head we easily added the below code to preload the LCP image for our next.js page.

From the WebPageTest result below, we can see that with preload, the banner image starts loading much earlier, and around 4 seconds it is fully loaded, whereas, without preload, it takes around 4.5 seconds.

Before(above)/After(below) of adding image preload

Word of caution ⏰ ⏰

Even though it might be tempting to preload all the in-viewport images, we should only preload the image corresponding to the LCP element. Otherwise, other preload images will compete with the LCP image for the bandwidth and LCP will be delayed.

3) Reduce image payload size

If we can reduce the image size without sacrificing the quality of the image then images will load faster. Earlier our product images were stored in the Amazon S3 bucket and banner images were stored in the Google cloud storage bucket.

Our media service team did a few benchmarks and noticed that moving images to NE Digital CDN(wrapper on top of CloudFront)helped to reduce TTFB, response time, and connect time.

benchmark between our CDN, S3 Bucket, and Google cloud storage bucket

After moving the images to CDN, our media service team implemented the following key optimizations to reduce the image size.

a) Serve WebP for supported browsers

WebP is a modern image format that can help reduce image size significantly over jpeg or png images. From Caniuse we noticed that around 90.2% of our user’s browsers were already supporting WebP. Based on this data we decided to use WebP for supported browsers.

WebP browser support

With our current implementation now users are served WebP if the request header contains image/webp, else users are served the original jpeg or png images. This did not require any code change on the frontend side since the on-the-fly conversion of jpeg/png to WebP was taking place on the CDN side.

From the below image we can see that the image size reduced from 306kb to 108kb when switched from jpeg to WebP.

The difference in banner image size jpeg vs WebP

b) Use of responsive images & responsive preload

For our website, all users were getting 1400px wide banner images and product images irrespective of their device. This resulted in a waste of network resources and adding further delay in loading images on mobile and tablets.

To solve this problem, we decided to implement responsive images using srcset to serve proper images based on screen size.

We also updated our code for preloading the LCP image so that it can preload responsive images. Yoav Weiss has written a great article on preloading responsive images which I highly recommend for further exploration.

From the below image we can see that the image size reduced from 108kb to 33kb when the image width(1424px) reduced to 800px(using w parameter) keeping the aspect ratio same.

The difference in banner image size with width parameter

c) Use of quality param

Another way of reducing image size is by reducing image quality. After having some discussion with our designers, we agreed to use quality value 70 for images that take a very big section of the viewport(for example banner images). For all other smaller section images we decided to use quality 60. Below we can see a sample code of using quality param in the banner image.

From the below image we can see that the banner image size reduced from 108kb to 85kb when quality is set to 70 via q=70 parameter.

The difference in banner image size with quality parameter

Initially, these image optimizations( WebP support, width, and quality parameter support) were implemented for the banner images alone. Once we moved these changes to production, from our PageSpeed dashboard we saw a significant reduction in image payload size.

image payload size-reduction after different optimizations

Once we verified positive results for the banner images, it was also rolled out for product images resulting in even further image size reduction.

4) Preconnect to CDN

Before the browser can request any image asset from our CDN, first it has to go through a few steps(DNS lookup, initial connection, SSL negotiation) to establish a secure connection. Each of these steps takes a certain amount of time depending on the network.

Since we already know that the browser needs to connect to our CDN for images, we can give this resource hint using preconnect and dns-prefetch so the browser can set up an early connection. The benefit of preconnect can be easily understood from the below image.

With/Without preconnect, Image credit: Milica Mihajlija

We are going to use dns-prefetch as a fallback for browsers that don’t support preconnect. Using next/head we can set it very easily in our _document.js as shown in the below code.

Let’s see the Results 🎊 🎊

image credit http://gph.is/1Ms9f3C

Here are the overall results after making all the changes mentioned above.

before/after image size of all the optmizations

Image payload on desktop homepage changed from 1332kb → 332kb (~75% reduction)

CrUX data for LCP for Fairprice

As per CrUX data users who had LCP below 2500ms changed from 66.89% → 81.65% (~14.75% improvement)

LCP and Speed Index score for desktop before and after the change

LCP on desktop homepage changed from 4044ms→ 1510ms (~62% reduction), Speed Index changed from 3193 → 2419 (~24% reduction)

LCP and Speed Index score for mobile before and after the change

LCP on mobile homepage changed from 14079ms→ 3860ms (~72% reduction), Speed Index changed from 9223 → 7859 (~15% reduction)

These results mentioned above are collected from our performance dashboard built using PageSpeed API data. You can check the below article to find how to set up a similar dashboard.

Bonus Tip

Next.js has recently released a built-in image component since v10 incorporating several image-related best practices. This component is built on top of <img> HTML tag and can help to improve LCP and CLS. I highly recommend reading this RFC to find out the key features and advantages of using it.

Final Thoughts

The key aspects of user experience that different performance metrics are trying to find can be seen from the below image.

image credit: https://web.dev/user-centric-performance-metrics/

While metrics like FCP, LCP, and Speed Index are useful to answer the first two questions in the above image, they can not capture information regarding the interactivity part.

Ensuring that the webpage becomes usable as soon as possible and provide a jank-free smooth user interaction is a key necessity to ensure the optimal user experience. Metrics like Total Blocking Time(lab metrics), First Input Delay(field metrics), and Time To Interactive help to determine different aspects of interactivity.

Reducing initial js payload size and using patterns like import on interaction helps to reduce js execution time and as a result, provide faster user interaction. You can read the below article to find out how we implemented those patterns for Fairprice.

That’s all, if you liked this article, please give it a clap & follow me on Medium or Twitter for more updates on similar topics.

Further Reads:

  1. Optimize images
  2. Optimize LCP
  3. Hybrid lazy loading
  4. Web almanac 2019
  5. NDTV LCP case study
  6. Tokopedia LCP case study

Thanks to Alvin choo, Zheng Ming New for the optimizations of our media service without which many of the image-related improvements wouldn’t have been possible.

--

--