← Back to Posts

Improving Image Load Speed by 90 to 95% (Without Sacrificing Quality)

3/24/2026

before improvement
before-optimization


Before: 2.4MB Image took 1.04s to load. Total Page Load for my very simple homepage took a staggering 1.4s in total. The image also flashes in after the 3 main buttons have already appeared.

So all around a pretty bad UX experience.

After:

after image load improvement
after-improvement


Experienced frontend engineers would have quickly noticed that now, we are loading in Webp format instead of PNG. And it only took 48ms in this instance. That's a 95% reduction from the previous version. And total homepage loaded in 456ms (previously 1.4s). For my very simple homepage, that's a 67.4% reduction.

Besides the switch from PNG to Webp, there are also a few other subtle changes.

1. WebP conversion is a framework-level optimization that didn't require the engineer to manually convert. And it doesn't sacrifice image quality.

2. Notice the Image Load in the 2nd version is shown much higher up in the Network Tab?

3. Even though the 34kb asset loads quickly under normal conditions, I tested it with network throttling to simulate slower connections. To handle those cases gracefully, I added a subtle fade-in transition — the image shifts from 0% to 100% opacity, appearing naturally rather than flashing in, which makes for a smoother user experience.


For #1, it's achieved by Next.js's built-in image optimization. Link to read more.

The Next.js <Image> component extends the HTML <img> element to provide:

Size optimization: Automatically serving correctly sized images for each device, using modern image formats like WebP.
Visual stability: Preventing layout shift automatically when images are loading.
Faster page loads: Only loading images when they enter the viewport using native browser lazy loading, with optional blur-up placeholders.
Asset flexibility: Resizing images on-demand, even images stored on remote servers.


#2 is achieved through adding priority on the <Image> Tag.

You should add the priority property to the image that will be the Largest Contentful Paint (LCP) element for each page. Doing so allows Next.js to specially prioritize the image for loading (e.g. through preload tags or priority hints), leading to a meaningful boost in LCP.
The LCP element is typically the largest image or text block visible within the viewport of the page. When you run next dev, you'll see a console warning if the LCP element is an <Image> without the priority property.


For users on slow 4G or 3G connections, I didn't want images to abruptly flash in after loading. Regardless of how long their network takes, a 300ms fade-in transition ensures the image appears gradually — fast enough to feel snappy, but smooth enough to avoid a jarring flash.

className={`object-cover object-center z-0 transition-opacity duration-300 ${imageReady ? "opacity-100" : "opacity-0"}`}