kebaux
About Archive

Next.js Image Optimization

Next.js Image Optimization

Next.js Image Optimization

TL;DR - for optimal speed and performance when loading collections of images, a CDN between the user’s browser and next.js is necessary.


I have a web application where I’m loading a collection of images (think image feed) and ran into a case where the load time of images was not only unreasonably long, but when hitting the same image collection page from multiple accounts, each account had to bear witness to the same insufferable tragedy of the images slowly popping into view.

.. next.js comes with fetch() and caching in combination with SSC, but I (naively) though that because the server itself was not delivered with the client app, and it was instead part of the shared hosted service, that the data fetching for one would take care of it for the rest. .. next.js resizes on the fly though and sends the result to the browser to cache, the next.js server itself does not cache .. a CDN between GCP and the next.js server wouldn’t help, since GCP already serves the images fast (that’s not the bottleneck)

It was odd to find the collection of images loading within 1-2 seconds in my local environment, but in staging the same collection of images took 10-30 seconds.

nextjs-01.png nextjs-02.png nextjs-03.png

Not only were the image load times egregious, but some images returned a 503 error (“Service unavailable”), suggesting there was a bottleneck wherein the server could not handle all the incoming requests.

To see if next.js and its image optimization strategy was the culprit, I added the `unoptimized` prop to the `<Image />` component and immediately saw the load time for the image collection in staging drop from the original 10-30 seconds to 1-2 seconds.

nextjs-unoptimized-01.png nextjs-unoptimized-02.png nextjs-unoptimized-03.png

<Image /> fetching with the `unoptimized` prop When we use the next.js <Image /> component with the `unoptimized` prop, the browser fetches the file directly from GCP, bypassing any resizing or compression. While we don’t pay any extra cost outside of the standard storage + egress for the direct download from GCP, our user takes the UX hit in having to download the full source, which in our case can be expensive (250kb - 6mb).

nextjs-image-sizes.png

By disabling cache in the network tab, we can forcibly load images directly from the server instead of utilizing the browser’s memory cache.

<Image /> fetching without the `unoptimized` prop When we use the next.js <Image /> component without the `unoptimized` prop, next.js will intercept the image request, fetch the original file from GCP, perform the resizing/compression, and then return the result to display to the user. The bottleneck here is that if we’re requesting many images at once, especially large images, we need to allocate enough resources in GCP to concurrently handle the request load.

There are clear reasons to use the next.js <Image /> component over the HTML <img> element (which <Image /> extends):

This brings up the refresher of why image optimization is important:

References

Date: 2025-02-25 Tue 12:32

Author: kebaux

Created: 2025-03-02 Sun 20:19

Other posts