Lighthouse 13's image check is one insight, "Improve image delivery", and its math fits in 30 lines. It assumes an ideal AVIF weighs 1/6 of a byte per pixel, adds the bytes wasted by pixels the screen never shows, and reports any image that would save more than 4,096 bytes. I reproduced it in Node on 4 images. On these files the estimate landed within 15% of what a real sharp AVIF encode saved for 3 of the 4.
Which audits does Lighthouse run on images now?
Lighthouse 13 folded the old modern-image-formats, uses-optimized-images, uses-responsive-images and offscreen-images audits into one insight, image-delivery-insight. Two image checks sit beside it: unsized-images (an <img> without width and height) and the LCP checks.
The insight's code lives in Chrome DevTools, in ImageDelivery.ts. I read it today. These are the numbers that drive it:
const TARGET_BYTES_PER_PIXEL_AVIF = 2 * 1 / 12; const GIF_SIZE_THRESHOLD = 100 * 1024; const BYTE_SAVINGS_THRESHOLD = 4096; const BYTE_SAVINGS_THRESHOLD_RESPONSIVE_BREAKPOINTS = 12288; How does the insight compute a saving?
For each image it paints, Lighthouse does three steps:
- Format or compression. If the file weighs more than 1/6 byte per pixel, the saving is the file size minus
pixels × 1/6. A JPEG or PNG gets "use a modern format"; a WebP or AVIF gets "adjust compression". - Size. If the file has more pixels than the screen shows, the wasted share is
1 - shown / file pixels, and that share of the bytes is a saving. An image withsrcsetor<picture>only counts if that saving tops 12,288 bytes. - Threshold. Any saving under 4,096 bytes is dropped.
The total compounds both: format saving, plus the wasted share of what is left. "Shown" means device pixels. The mobile run emulates a 412 px wide screen at a device pixel ratio of 1.75 (from Lighthouse's constants.js), so a 360 CSS px image fills 630 device pixels.
The script
The page I modelled has 4 images, with the CSS sizes they would get on a phone. The script applies the formula, flags images without size attributes, and then does what Lighthouse only estimates: it encodes a real AVIF at the shown width with sharp.
import sharp from 'sharp'; import { readFile } from 'node:fs/promises'; const DPR = 1.75; // Lighthouse mobile emulation const AVIF_BPP = 2 * 1 / 12; // TARGET_BYTES_PER_PIXEL_AVIF const MIN = 4096; // BYTE_SAVINGS_THRESHOLD const page = [ { src: 'apps/web/public/images/index/image-compressor-sample-camera-photo.jpeg', css: [412, 231], attrs: true }, { src: 'packages/engine/tests/fixtures/photo.png', css: [360, 270], attrs: false }, { src: 'packages/engine/tests/fixtures/photo.webp', css: [360, 270], attrs: true }, { src: 'packages/engine/tests/fixtures/photo.avif', css: [360, 270], attrs: false }, ]; for (const img of page) { const buf = await readFile(img.src); const { width, height, format } = await sharp(buf).metadata(); const bytes = buf.length, filePx = width * height; const shownPx = Math.round(img.css[0] * DPR) * Math.round(img.css[1] * DPR); let fmt = 0; if (bytes / filePx > AVIF_BPP) fmt = bytes - Math.round(AVIF_BPP * filePx); const waste = Math.max(0, 1 - shownPx / filePx); const size = Math.round(waste * bytes); const total = fmt + Math.round(waste * (bytes - fmt)); const flagged = [fmt, size].some((s) => s > MIN); const real = await sharp(buf).resize(Math.round(img.css[0] * DPR)).avif({ quality: 50 }).toBuffer(); console.log(JSON.stringify({ file: img.src.split('/').pop(), format, width, height, bytes, bpp: +(bytes / filePx).toFixed(3), fmtSave: fmt, sizeSave: size, estTotal: flagged ? total : 0, unsized: !img.attrs, realAvifAtDisplay: real.length, realSave: bytes - real.length })); } It printed:
{"file":"image-compressor-sample-camera-photo.jpeg","format":"jpeg","width":4964,"height":2782,"bytes":9804300,"bpp":0.71,"fmtSave":7502659,"sizeSave":9597503,"estTotal":9755753,"unsized":false,"realAvifAtDisplay":14872,"realSave":9789428} {"file":"photo.png","format":"png","width":800,"height":600,"bytes":1382351,"bpp":2.88,"fmtSave":1302351,"sizeSave":524170,"estTotal":1332686,"unsized":true,"realAvifAtDisplay":25003,"realSave":1357348} {"file":"photo.webp","format":"webp","width":800,"height":600,"bytes":234858,"bpp":0.489,"fmtSave":154858,"sizeSave":89055,"estTotal":185193,"unsized":false,"realAvifAtDisplay":20019,"realSave":214839} {"file":"photo.avif","format":"heif","width":800,"height":600,"bytes":175277,"bpp":0.365,"fmtSave":95277,"sizeSave":66463,"estTotal":125612,"unsized":true,"realAvifAtDisplay":24662,"realSave":150615} How close is the estimate to a real encode?
| Image | Bytes | Lighthouse estimate | Real AVIF saving | Unsized |
|---|---|---|---|---|
| camera photo, 4964 × 2782 JPEG | 9,804,300 | 9,755,753 | 9,789,428 | no |
photo.png, 800 × 600 | 1,382,351 | 1,332,686 | 1,357,348 | yes |
photo.webp, 800 × 600 | 234,858 | 185,193 | 214,839 | no |
photo.avif, 800 × 600 | 175,277 | 125,612 | 150,615 | yes |
The estimate ran low every time, by 24.7 KB to 33.7 KB. The reason is the 1/6 byte per pixel target: sharp's AVIF at quality 50 came out between 0.05 and 0.08 byte per pixel at the shown size. The 1/6 figure is deliberately pessimistic, and the source comments say so.
Note: The AVIF file was flagged too. People read "use a modern format" and assume an AVIF passes. The insight checks bytes per pixel and shown size, not the format, so an oversized AVIF still gets an "adjust compression" and a resize saving.
What about the unsized-images audit?
That one needs no math. An <img> without both width and height attributes (or a CSS size) lets the layout shift when the file arrives. In my page, photo.png and photo.avif had none, so both would be listed. Add the file's intrinsic size as attributes and let CSS scale it:
<img src="photo.avif" width="800" height="600" style="width:100%;height:auto" alt="..."> When should you trust the estimate?
Use it to rank images, not to quote savings. The ranking held here: the camera photo dwarfs everything, the PNG comes next. The absolute numbers depend on the real encoder and quality you pick. If you want measured numbers instead of the formula, run the encode yourself, as the script does, or paste the page address into the Blinkimg image analyzer. It runs every image on the page through the real engine, and it also fetches Google's PageSpeed Insights image audits for the page (mobile strategy) and shows them under Google's name.
How I measured
- Machine: Apple M1 Pro, macOS 15.7.3, on 2026-10-17.
- Node 24.22.0, sharp 0.35.4, libvips 8.18.6.
- Formula and thresholds from
ImageDelivery.tsin devtools-frontendmain, and the 1.75 device pixel ratio from Lighthouseconstants.js, both read on 2026-10-17. - Files: the site's
image-compressor-sample-camera-photo.jpegand the engine fixturesphoto.png,photo.webp,photo.avif. - The CSS sizes are the ones I chose for a 412 px wide phone layout.
What I did not test
I did not run Lighthouse itself, so a real trace could differ in the shown size it measures. The real saving uses one AVIF quality (50); a stricter quality would shrink the gap.
I build Blinkimg, an image compressor and converter with a developer API. I wrote this article with help from an AI assistant (Claude), which ran the code on my machine; every number in it comes from those runs.




