Improve image delivery: how to fix this Lighthouse notice

Your LCP waits for the photo at the top of the page, and every image downloading with it slows it down. What each line asks, why a WebP can still be listed, and the fix for WordPress, Shopify and code.

  • Good ≤ 2.5 s
  • Needs improvement ≤ 4 s
  • Poor > 4 s
Arjen Karel
Updated · 9 min read
Share

What does Improve image delivery want you to change?

Improve image delivery is the notice in PageSpeed Insights and Lighthouse 13 that replaced Properly size images and Serve images in next-gen formats. It lists the photos on your page that are bigger or heavier than a phone needs. Serve each photo at the width it is shown, about 720 pixels for a photo across a phone screen, as WebP or AVIF with a JPEG fallback.

Just want the quick fix? Jump to WordPress, Shopify, GIFs or the code.

What do the lines under Improve image delivery mean?

Of the photos the browser drew while your page loaded, Lighthouse lists the ones that could be lighter, with the file size (Resource Size) and what a better file would save (Est Savings). The notice says: "Reducing the download time of images can improve the perceived load time of the page and LCP." Under each photo are one or two of these lines:

Lighthouse report, Improve image delivery in red with Est savings of 4,354 KiB and the labels FCP, LCP and Unscored: an animated GIF with the video line, a PNG photo with the modern format and size lines, and the header photo with the line that the 2560x1707 file is larger than its displayed dimensions of 721x481

My test blog post before any fix, in a Lighthouse run as PageSpeed Insights does it.

  • "Using a modern image format (WebP, AVIF) or increasing the image compression could improve this image’s download size": the photo is a JPEG or PNG and too heavy for its pixels; WebP, AVIF or a lower quality makes it lighter.
  • "Increasing the image compression factor could improve this image’s download size": the photo is already WebP or AVIF and still too heavy for its pixels; lower its quality.
  • "Using video formats instead of GIFs can improve the download size of animated content": a GIF over 100 KiB. A still GIF gets it too; save that one as PNG or WebP.
  • "This image file is larger than it needs to be (WxH) for its displayed dimensions (WxH)": the file has more pixels than the screen shows; serve smaller screens a smaller copy.

Lighthouse tests on a phone screen 412 CSS pixels wide with 1.75 screen pixels per CSS pixel, and gives the shown size in screen pixels: a photo across that screen needs 721. For the weight, every format gets the same target of about one byte per six pixels.

Red means Lighthouse expects a faster FCP or LCP from smaller files, usually because a listed photo is in the first screen; orange means photos are listed but the first screen doesn't wait for them. The estimate can change between runs. Unscored: the notice adds nothing to the score itself, but a lighter LCP photo lowers your LCP, which counts for 25% of the score. Before Lighthouse 13 these lines were four audits: Serve images in next-gen formats (later Serve images in modern formats), Efficiently encode images, Properly size images and Use video formats for animated content.

Why does one large photo slow down your LCP?

On a phone, your visitor sees the title and the first lines of text under a white space where the header photo goes. Then the photo fills in from the top down.

Phone screen of a hiking blog post 2 seconds after the tap: the title and the text are there, the space for the header photo above them is still white

Before

The same phone screen 2 seconds after the tap with the header photo of a mountain ridge already shown above the title

After

The test blog post on a throttled phone connection, 2 seconds after the tap.

I measured it on a hiking blog post set up like a WordPress site: photos straight from the camera, a 3.5 MB GIF of the trail and one photo saved as PNG. Your LCP waits for one file, the largest photo in the first screen, but every file downloading at the same time shares the phone's connection. On the test post the GIF, the PNG and two other photos downloaded alongside the header photo, the lazy ones too, because Chrome also loads lazy images close to the screen. The header photo took almost 11 seconds.

ChangeLCPHeader photoImages and video
Before11.3 s623 kB4,957 kB
Header and author photo at phone size1.5 s56 kB4,306 kB
Every photo as WebP1.4 s29 kB3,625 kB
The GIF as a lazy video1.4 s29 kB478 kB

The test post on slow 4G with Lighthouse's phone screen, median of three loads per row.

The size of the header photo did almost all of it; WebP saved another tenth of a second. The GIF was the largest saving in bytes and changed nothing for LCP. On a fast connection the wait is much shorter, and LCP breakdown shows how much of your LCP the download is. According to the State of Web Vitals, of the LCP images that are a plain <img> without srcset, 38% have more than twice the pixels a phone needs.

Why is a photo still listed after you converted it to WebP?

Improve image delivery in orange with Est savings of 3,710 KiB: a photo of a mountain lake whose img has a srcset and sizes attribute, still listed with the modern format line and the line that its 768x479 file is larger than its displayed dimensions of 665x415

The test post after the size fix: orange, with a srcset photo still listed.

The page still sends the JPEG. A plugin makes WebP files next to your originals, but the page, the page cache or the CDN keeps serving the old file, and DevTools shows type jpeg. Clear the page cache and the CDN after you switch conversion on, and check background photos a page builder sets in CSS: a plugin that rewrites the HTML often misses them.

The WebP is still too heavy. Lighthouse uses the same weight target for WebP and AVIF as for a JPEG. Lower the quality setting of the plugin or your export, from 90 to around 75, and check by eye that the photo still looks right.

The photo has a srcset and is still too large. WordPress lists its copies (300, 768, 1024, 1536 and 2048 pixels wide) in the srcset, and the browser takes the first one wider than the sizes attribute says the photo is shown. When sizes says 100vw for a narrower column, it takes a far larger copy: fix sizes first, on a block theme with the Enhanced Responsive Images plugin. Even with sizes right, the test post's column needs 665 screen pixels, so the browser takes the 768 and Lighthouse lists the extra 17 KiB, over the 12 KiB it allows with a srcset. A copy just wider than the column, added with add_image_size() in the theme, closes that gap.

How do you fix Improve image delivery?

Start with the photo in the first screen, usually the header photo: your LCP waits for it. Size first: the right width saves more than a new format.

WordPress

WordPress makes smaller copies of every upload, and the block editor gives images a srcset so the browser picks one that fits. A theme or page builder that prints the full upload sends every phone the largest file. A listed URL ending in -scaled.jpg, or without a size such as -768x512 in its name, is the full upload.

  • Image block: under Resolution, choose Large instead of Full Size.
  • Elementor background: set Image Resolution to a smaller size and give the mobile view its own photo.
  • A theme's header or featured image: when it prints one large file, ask the theme developer, or your own, to print it with the_post_thumbnail(), which adds the srcset.

For the format, let WordPress make the WebP or AVIF files on your own server. Tools > Site Health > Info > Media Handling lists what your server's image library can write; your host can add what is missing. The WordPress Performance Team's Modern Image Formats plugin then makes a WebP or AVIF of every new upload, and of older ones when you run the Regenerate Thumbnails plugin. Delivery goes through Modern Image Formats or your CDN: in the plugin, switch on Output fallback images and Picture element under Settings > Media, so browsers that can't show the format get the JPEG. When your host can't add the format, a plugin that converts in its own cloud, such as Imagify or ShortPixel, does the work.

WP Rocket's WebP option converts nothing: it only keeps a separate page cache for browsers that get WebP. After a theme switch, regenerate the thumbnails: old uploads lack the new theme's sizes, so WordPress serves the full upload.

Shopify

Shopify's CDN already sends WebP, at the width the theme asks for. What is left for you:

  • Sections and apps that ask for one large file: an image banner from an app, a reviews widget, an older theme's custom section. The listed URL then has a large width= value, or none. Ask the app or the theme developer for a version with responsive images, or replace it.
  • Very large uploads: a URL without a width returns the full size you uploaded. Upload photos at about 2,048 to 2,560 pixels wide.
  • The compression line: Shopify has no quality setting, so upload an original saved at a lower quality, in Squoosh for example.

A section that prints one size needs a developer: one or two lines per section, shown under the code.

GIFs and animations

Replace the GIF with a video that starts by itself, without sound, and loops: on the test post a 337 kB MP4 of the same clip replaced a 3.5 MB GIF. WordPress 7.1 turns an animated GIF into a video when you upload it to an Image block in Chrome or Edge. In older versions, convert it first and use a Video block with Autoplay, Loop, Muted and Play inline switched on. On Shopify, upload it as a video.

Give a video below the first screen loading="lazy". Without it, the video and its poster download with the page and share the connection with your header photo: on the test post that made LCP 0.26 seconds slower than the GIF. With it, LCP was back at 1.4 seconds. Chrome supports it from version 148; other browsers load the video as before. In WordPress, put the code below in a Custom HTML block.

<video autoplay muted loop playsinline loading="lazy" width="640" height="360" poster="/img/ridge-walk.jpg">
  <source src="/img/ridge-walk.mp4" type="video/mp4">
</video>

To make the MP4 yourself:

ffmpeg -i ridge-walk.gif -movflags +faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ridge-walk.mp4

The code

Give the browser several widths and tell it how wide the photo is on screen. sizes describes the layout: a photo in a 720-pixel column with 16 pixels of padding on each side on a phone is not 100vw.

<img src="/img/lake-1024.jpg"
  srcset="/img/lake-480.jpg 480w, /img/lake-720.jpg 720w, /img/lake-1024.jpg 1024w, /img/lake-1440.jpg 1440w"
  sizes="(max-width: 752px) calc(100vw - 32px), 720px"
  width="1024" height="639" alt="A mountain lake on the second day" loading="lazy" decoding="async">

For AVIF and WebP, put the image in a <picture>. The browser takes the first source it can show, and the <img> is the JPEG fallback. For the header photo, add fetchpriority="high" and leave out loading="lazy" (LCP request discovery explains why).

<picture>
  <source type="image/avif" srcset="/img/ridge-720.avif 720w, /img/ridge-1080.avif 1080w, /img/ridge-1440.avif 1440w" sizes="100vw">
  <source type="image/webp" srcset="/img/ridge-720.webp 720w, /img/ridge-1080.webp 1080w, /img/ridge-1440.webp 1440w" sizes="100vw">
  <img src="/img/ridge-1080.jpg" srcset="/img/ridge-720.jpg 720w, /img/ridge-1080.jpg 1080w, /img/ridge-1440.jpg 1440w" sizes="100vw"
    width="1440" height="960" alt="Mountain ridge above the sea" fetchpriority="high">
</picture>

To make the files:

cwebp -q 75 ridge-1080.jpg -o ridge-1080.webp
avifenc -q 50 ridge-1080.jpg ridge-1080.avif

On Shopify, image_url and image_tag write the srcset for you:

{{ section.settings.image | image_url: width: 1440 | image_tag: widths: '480, 720, 1080, 1440', sizes: '100vw', fetchpriority: 'high' }}

In Next.js, give next/image a sizes value that matches the layout: without it, an image with fill counts as the full screen width. formats: ['image/avif', 'image/webp'] under images in next.config.js sends AVIF where the browser accepts it, with WebP or the original as fallback.

Photos you can't change

An Instagram feed, a reviews widget or an ad shows photos from another server. Keep the widget out of the first screen, or show photos you host yourself.

Do your visitors get the smaller file now?

Run Lighthouse again and see which photos are left. Then check what the browser received: open DevTools (F12), choose Network, click Img and reload the page. The Type column shows webp or avif for each photo, the Size column its bytes.

Chrome DevTools Network panel filtered on Img: the header photo, the author photo and two other photos with type webp, and the animated GIF with type gif at 3,506 kB

The test post after the WebP step.

When your server or CDN picks the format, the first request should return AVIF or WebP, the second the JPEG:

curl -sI -H "Accept: image/avif,image/webp,*/*" https://www.example.com/img/ridge-1080.jpg | grep -i content-type
curl -sI -H "Accept: image/jpeg" https://www.example.com/img/ridge-1080.jpg | grep -i content-type

For your real visitors, CoreDash groups LCP on phones by the LCP image. Look for full-size uploads, JPEGs and PNGs near the top of the list, and check after the fix that the same page now shows the smaller file. Search Console takes up to 28 days to show the change.

CoreDash LCP table on mobile grouped by LCP Image url: per LCP image its thumbnail, file name, p75 LCP, rating, distribution of good, needs improvement and poor page loads, trend and sessions

Sometimes the large photo comes from a theme, a page builder or an app nobody on your team can change, or LCP stays slow with small photos because the delay is in another part of LCP. In a Core Web Vitals power hour we open your own page and find what your LCP is waiting for.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

Need a faster site?

I'm Arjen, and I've been fixing slow sites for 17 years. If you're running into issues you can't fix yourself, send me your URL and let's have a chat.

Send me your URL
Arjen Karel
Arjen Karel, Web performance consultant since 2009

Why isn't our fix working?

Sometimes the CDN still serves the old HTML, or the fix went into the desktop template and mobile uses another one. Send me your URL and the problem, and I reply with a few times. I check your field data first, then we spend one hour on your site, and within a day you have my notes in writing. €350 excl. VAT.

Send me your URL

No developer to build the fix? I do the work myself, on a copy of your site.