Performance 22 min read

Lighthouse Image Audit: Fix Every Image Warning

Fix all Lighthouse image-related warnings and opportunities. Step-by-step solutions for properly size images, serve modern formats, defer offscreen images, and more.

By ImageGuide Team · Published February 15, 2026
lighthouseperformance auditweb performanceimage optimizationcore web vitals

Lighthouse flags image issues that directly hurt your performance score and user experience. This guide covers every image-related audit, explains exactly what triggers each one, and provides copy-paste solutions to fix them.

Understanding Lighthouse Image Audits

Lighthouse groups image findings into two categories:

CategoryMeaningScore Impact
OpportunitiesChanges that could improve load timeDirectly reduces Performance score
DiagnosticsInformation about best practicesMay affect score indirectly

Where Image Audits Appear

Image-related findings show up across multiple sections of a Lighthouse report:

  • Performance > Opportunities: Properly size images, serve next-gen formats, defer offscreen images, efficiently encode images
  • Performance > Diagnostics: Avoid enormous network payloads, image elements do not have explicit width and height
  • Best Practices: Image display dimensions, image aspect ratio
  • SEO: Image elements do not have alt attributes (covered in our alt text guide)

How Scoring Works

Lighthouse calculates potential savings in bytes for each opportunity. The more bytes you could save, the more the audit impacts your score. An image opportunity saving 2 MB matters far more than one saving 20 KB.

The Performance score weighs metrics, not audits directly. But fixing image audits improves the underlying metrics:

Image FixMetric ImprovedTypical Impact
Compress imagesLCP, Speed Index0.5-3s faster
Modern formatsLCP, Speed Index0.3-2s faster
Lazy load offscreenSpeed Index, TTI0.5-4s faster
Preload LCP imageLCP0.2-1s faster
Set dimensionsCLS0.05-0.3 CLS reduction

”Properly Size Images”

What Triggers This Audit

Lighthouse flags images where the rendered size is significantly smaller than the downloaded size. If you download a 2000px-wide image but display it at 400px, Lighthouse calculates the wasted bytes.

The audit triggers when an image could save at least 4 KiB by being properly sized.

The exact Lighthouse message:

Properly size images — Serve images that are appropriately-sized to save cellular data and improve load time.

Why It Matters

Oversized images are the single most common performance problem. A 4000x3000 photo displayed at 800x600 wastes 93% of its pixels.

How to Calculate Correct Sizes

Step 1: Determine the rendered size in CSS pixels:

// In DevTools Console
document.querySelectorAll('img').forEach(img => {
  const rendered = `${img.clientWidth}x${img.clientHeight}`;
  const natural = `${img.naturalWidth}x${img.naturalHeight}`;
  const ratio = ((img.naturalWidth * img.naturalHeight) /
    (img.clientWidth * img.clientHeight)).toFixed(1);
  console.log(`${img.src}\n  Rendered: ${rendered}, Natural: ${natural}, Oversized: ${ratio}x`);
});

Step 2: Account for device pixel ratio (DPR). Retina displays need 2x pixels:

DisplayDPRCSS WidthImage Width Needed
Standard1x800px800px
Retina2x800px1600px
High-end mobile3x400px1200px

Rule of thumb: Serve images at 2x the rendered CSS width. Going to 3x adds file size with minimal visual benefit.

The Fix: Responsive Images

<!-- BEFORE: One massive image for everyone -->
<img src="hero-4000.jpg" alt="Hero image">

<!-- AFTER: Responsive images with srcset -->
<img
  src="hero-800.jpg"
  srcset="
    hero-400.jpg 400w,
    hero-800.jpg 800w,
    hero-1200.jpg 1200w,
    hero-1600.jpg 1600w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
  alt="Hero image"
  width="800"
  height="450"
>

Generating Responsive Variants

With Sharp (Node.js):

const sharp = require('sharp');

const widths = [400, 800, 1200, 1600];

async function generateResponsive(inputPath, outputDir) {
  for (const width of widths) {
    await sharp(inputPath)
      .resize(width, null, { withoutEnlargement: true })
      .jpeg({ quality: 80, mozjpeg: true })
      .toFile(`${outputDir}/hero-${width}.jpg`);

    // Also generate WebP variants
    await sharp(inputPath)
      .resize(width, null, { withoutEnlargement: true })
      .webp({ quality: 80 })
      .toFile(`${outputDir}/hero-${width}.webp`);
  }
}

generateResponsive('./hero-original.jpg', './dist/images');

With a CDN like Sirv, you skip generating variants entirely:

<img
  src="https://yoursite.sirv.com/hero.jpg?w=800"
  srcset="
    https://yoursite.sirv.com/hero.jpg?w=400 400w,
    https://yoursite.sirv.com/hero.jpg?w=800 800w,
    https://yoursite.sirv.com/hero.jpg?w=1200 1200w,
    https://yoursite.sirv.com/hero.jpg?w=1600 1600w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
  alt="Hero image"
  width="800"
  height="450"
>

Sirv dynamically resizes on request and caches the result at the edge. No build step, no stored variants.

Savings Calculation

ScenarioOriginalProperly SizedSavings
4000px image at 800px render2.4 MB180 KB92%
2000px hero at 1200px render800 KB320 KB60%
3000px thumbnail at 200px render1.8 MB15 KB99%

“Serve Images in Next-Gen Formats”

What Triggers This Audit

Lighthouse flags images served as JPEG, PNG, or GIF when WebP or AVIF would be significantly smaller.

The exact Lighthouse message:

Serve images in next-gen formats — Image formats like WebP and AVIF often provide better compression than PNG or JPEG, which means faster downloads and less data consumption.

Why It Matters

Modern formats deliver the same visual quality at substantially smaller file sizes:

FormatTypical Savings vs JPEGBrowser Support
WebP25-35% smaller97% globally
AVIF40-50% smaller92% globally

The Fix: <picture> Element

The <picture> element lets browsers choose the best supported format:

<picture>
  <!-- AVIF: best compression, good support -->
  <source srcset="hero.avif" type="image/avif">
  <!-- WebP: great compression, widest support -->
  <source srcset="hero.webp" type="image/webp">
  <!-- JPEG: fallback for all browsers -->
  <img src="hero.jpg" alt="Hero image" width="1200" height="600">
</picture>

With Responsive Images

Combine format negotiation with responsive sizing:

<picture>
  <source
    type="image/avif"
    srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"
    sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
  >
  <source
    type="image/webp"
    srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
    sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
  >
  <img
    src="hero-800.jpg"
    srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
    sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
    alt="Hero image"
    width="1200"
    height="600"
    loading="lazy"
  >
</picture>

Accept Header Negotiation (Server-Side)

Instead of multiple <source> elements, let the server decide based on the browser’s Accept header:

# Nginx configuration
map $http_accept $webp_suffix {
  default "";
  "~*image/avif" ".avif";
  "~*image/webp" ".webp";
}

location ~* \.(jpg|jpeg|png)$ {
  # Try AVIF first, then WebP, then original
  try_files $uri$webp_suffix $uri =404;
  add_header Vary Accept;
}

Similar rules can be configured for Apache using mod_rewrite to check the Accept header and serve .avif or .webp variants when they exist on disk.

CDN Auto-Format (Easiest Solution)

With Sirv, Cloudinary, or similar CDNs, format negotiation happens automatically:

<!-- Sirv auto-detects browser support and serves the optimal format -->
<img src="https://yoursite.sirv.com/hero.jpg?w=1200" alt="Hero image">

When Chrome requests this URL, Sirv returns AVIF. When Safari requests it, Sirv returns WebP. No <picture> element needed. The Vary: Accept header ensures caches store separate versions per format.

”Efficiently Encode Images”

What Triggers This Audit

Lighthouse re-compresses each image with a quality of 85 and checks if the result is significantly smaller than the served version. If it is, the image is flagged.

The exact Lighthouse message:

Efficiently encode images — Optimized images load faster and consume less cellular data.

Why It Matters

Many images are saved at quality 100 or with unoptimized encoding settings. Simply re-encoding with modern settings can reduce file size 30-60% with no visible quality loss.

FormatToolQuality SettingExpected Savings
JPEGMozJPEG80-8530-50% vs q100
JPEGlibjpeg-turbo80-8520-40% vs q100
WebPlibwebp75-8225-35% vs JPEG
AVIFlibavif50-6540-50% vs JPEG
PNGpngquant + oxipngQuality 80 + strip60-80% vs unoptimized

Quick Fix with Sharp

const sharp = require('sharp');
const { globSync } = require('glob');

// Batch re-encode all JPEGs with MozJPEG
for (const img of globSync('src/images/**/*.{jpg,jpeg}')) {
  sharp(img)
    .jpeg({ quality: 82, mozjpeg: true, progressive: true })
    .toFile(img.replace('src/images', 'dist/images'));
}

Common Causes of Inefficient Encoding

  1. Exported from Photoshop at quality 100: Default exports are often unoptimized
  2. Screenshots saved as PNG: Use JPEG/WebP for screenshot photos, PNG only for sharp text
  3. Stock photos used as-is: Stock sites optimize for quality, not web performance
  4. CMS uploads without optimization: No compression applied on upload
  5. Missing metadata stripping: EXIF data can add 50-500 KB

”Defer Offscreen Images”

What Triggers This Audit

Lighthouse flags images that are loaded immediately but are positioned below the fold (outside the initial viewport).

The exact Lighthouse message:

Defer offscreen images — Consider lazy-loading offscreen and hidden images after all critical resources have finished loading to lower time to interactive.

Why It Matters

Loading all images upfront delays the page. On a blog post with 15 images, 80% of them are below the fold. Loading them all immediately wastes bandwidth and competes with critical resources.

The Fix: Native Lazy Loading

<!-- Above the fold: load immediately -->
<img src="hero.jpg" alt="Hero" width="1200" height="600">

<!-- Below the fold: lazy load -->
<img src="blog-photo-1.jpg" alt="..." width="800" height="450" loading="lazy">
<img src="blog-photo-2.jpg" alt="..." width="800" height="450" loading="lazy">
<img src="blog-photo-3.jpg" alt="..." width="800" height="450" loading="lazy">

Critical Rule: Never Lazy Load Above-the-Fold Images

Lazy loading the LCP image delays its appearance and hurts your LCP score:

<!-- WRONG: lazy loading the hero image -->
<img src="hero.jpg" loading="lazy" alt="Hero">

<!-- CORRECT: hero loads immediately, below-fold is lazy -->
<img src="hero.jpg" alt="Hero" fetchpriority="high" width="1200" height="600">

Intersection Observer (Custom Control)

For more control over loading thresholds, use the Intersection Observer API instead of native lazy loading:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      if (img.dataset.srcset) img.srcset = img.dataset.srcset;
      observer.unobserve(img);
    }
  });
}, { rootMargin: '200px 0px' }); // Start loading 200px before visible

document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));

How Many Images to Eagerly Load

A practical guideline based on page layout:

Page TypeEager LoadLazy Load
Landing page (hero only)1 imageEverything else
Blog postFirst 1-2 imagesRemaining images
Product pageMain product imageGallery, related items
Image galleryFirst 4-8 thumbnailsRemaining thumbnails
Infinite scrollFirst screenEverything below

”Image Elements Do Not Have Explicit Width and Height”

What Triggers This Audit

Lighthouse flags <img> elements that are missing width and height attributes.

The exact Lighthouse message:

Image elements do not have explicit width and height — Set an explicit width and height on image elements to reduce layout shifts and improve CLS.

Why It Matters

Without dimensions, the browser does not know how much space to reserve for an image. When the image finally loads, everything below it shifts down. This causes Cumulative Layout Shift (CLS), one of the three Core Web Vitals.

The Fix: Always Set Width and Height

<!-- WRONG: No dimensions -->
<img src="photo.jpg" alt="Photo">

<!-- CORRECT: Dimensions set -->
<img src="photo.jpg" alt="Photo" width="800" height="450">

The browser uses these attributes to calculate the aspect ratio and reserve space before the image loads.

Making It Responsive

Setting width and height does not mean the image renders at fixed dimensions. Use CSS to make it responsive:

img {
  max-width: 100%;
  height: auto;
}

With this CSS, the browser:

  1. Reads width="800" and height="450" to calculate aspect ratio (16:9)
  2. Reserves space at that aspect ratio within the available width
  3. Renders the image responsively without layout shift

Using CSS aspect-ratio

For cases where you know the aspect ratio but not exact pixel dimensions:

.hero-image {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
  object-fit: cover;
}

.thumbnail {
  aspect-ratio: 1 / 1;
  width: 200px;
  height: auto;
  object-fit: cover;
}

.portrait {
  aspect-ratio: 3 / 4;
  width: 100%;
  height: auto;
  object-fit: cover;
}

Common Aspect Ratios

RatioUse CaseWidthHeight
16:9Hero images, video thumbnails1600900
4:3Product photos, blog images1200900
1:1Avatars, social thumbnails800800
3:2Photography, DSLR default1200800
21:9Panoramic banners2100900

Framework-Specific Solutions

Most modern frameworks enforce dimensions automatically. Next.js requires width and height as props on its <Image> component (or uses fill mode with a sized parent). Astro infers dimensions from imported images. React frameworks generally make it harder to forget dimensions than plain HTML.

”Preload Largest Contentful Paint Image”

What Triggers This Audit

Lighthouse flags when the LCP element is an image that is not preloaded or given high fetch priority, and the LCP time is slow.

The exact Lighthouse message:

Preload Largest Contentful Paint image — Preload the image used by the LCP element in order to improve your LCP time.

Why It Matters

The browser discovers images in a specific order:

  1. Parse HTML (finds <img> tags)
  2. Parse CSS (finds background-image)
  3. Execute JavaScript (finds dynamically inserted images)

If the LCP image is a CSS background or loaded by JavaScript, the browser discovers it late. Preloading tells the browser to fetch it immediately.

<head>
  <!-- Preload a standard image -->
  <link rel="preload" as="image" href="/images/hero.jpg">

  <!-- Preload with format hint -->
  <link rel="preload" as="image" href="/images/hero.webp" type="image/webp">

  <!-- Preload responsive image -->
  <link
    rel="preload"
    as="image"
    href="/images/hero-1200.jpg"
    imagesrcset="/images/hero-400.jpg 400w, /images/hero-800.jpg 800w, /images/hero-1200.jpg 1200w"
    imagesizes="100vw"
  >
</head>

The Fix: fetchpriority=“high”

For images already in HTML (not CSS backgrounds), fetchpriority can be more effective:

<img
  src="hero.jpg"
  fetchpriority="high"
  alt="Hero image"
  width="1200"
  height="600"
>

When to Use Each

ScenarioSolution
LCP is an <img> in HTMLfetchpriority="high" on the img
LCP is a CSS background-image<link rel="preload"> in head
LCP is loaded by JavaScript<link rel="preload"> in head
LCP image URL is dynamic (CMS)Generate preload tag server-side
LCP is on a CDN with auto-formatPreload with imagesrcset

Example: Preloading a CDN Image

When using an image CDN like Sirv, preload the base URL and let the CDN handle format negotiation:

<head>
  <link
    rel="preload"
    as="image"
    href="https://yoursite.sirv.com/hero.jpg?w=1200"
    imagesrcset="
      https://yoursite.sirv.com/hero.jpg?w=400 400w,
      https://yoursite.sirv.com/hero.jpg?w=800 800w,
      https://yoursite.sirv.com/hero.jpg?w=1200 1200w
    "
    imagesizes="100vw"
  >
</head>

Common Mistakes

  1. Preloading non-LCP images: Only preload the LCP element. Preloading too many resources defeats the purpose.
  2. Preloading lazy-loaded images: If an image has loading="lazy", do not preload it.
  3. Mismatched URLs: The preload href must match the actual src exactly, including query parameters.
  4. Missing as="image": Without this attribute, the browser fetches with wrong priority.

”Avoid Enormous Network Payloads”

What Triggers This Audit

Lighthouse flags pages where total network transfer exceeds 5 MB. Images are almost always the largest contributor.

The exact Lighthouse message:

Avoid enormous network payloads — Large network payloads cost users real money and are highly correlated with long load times.

Image Contribution to Page Weight

On a typical page, images account for 50-80% of total transfer size:

Page TypeTotal WeightImage WeightImage Share
News article3.2 MB2.1 MB66%
E-commerce product4.5 MB3.8 MB84%
Portfolio8.2 MB7.5 MB91%
SaaS landing page2.8 MB1.4 MB50%

Image Budgets

Set budgets based on page type and target audience:

Page TypeImage BudgetPer-Image MaxNotes
Mobile landing page500 KB150 KBFast 3G users
Blog post1 MB200 KBMultiple content images
Product page800 KB250 KBHigh quality matters
Gallery page1.5 MB initial100 KB/thumbLazy load the rest
App dashboard300 KB50 KBIcons and avatars

Progressive Loading Strategies

Instead of loading one enormous image, break the experience into stages:

Low-Quality Image Placeholder (LQIP):

<!-- Inline a tiny blurred placeholder, load full image lazily -->
<div class="image-wrapper">
  <img
    src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
    data-src="photo-full.jpg"
    alt="Photo"
    width="800"
    height="450"
    class="lazy-image"
    style="filter: blur(20px); transition: filter 0.3s"
  >
</div>
// Replace placeholder with full image
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.onload = () => img.style.filter = 'none';
      observer.unobserve(img);
    }
  });
});

document.querySelectorAll('.lazy-image').forEach(img => observer.observe(img));

Other placeholder strategies include dominant color backgrounds (set background-color on the container to the image’s primary color) and BlurHash (compact blur representations generated at build time).

”Does Not Use Passive Listeners to Improve Scrolling Performance”

Some older lazy loading libraries attach non-passive scroll event listeners, which block smooth scrolling. If your lazy loading uses window.addEventListener('scroll', handler), either add { passive: true } as the third argument, switch to Intersection Observer, or use native loading="lazy" (the best option). Modern lazy loading should never use scroll listeners.

Running Lighthouse Programmatically

Lighthouse CLI

# Install globally
npm install -g lighthouse

# Run audit
lighthouse https://example.com --output=json --output-path=./report.json

# Performance only, with specific categories
lighthouse https://example.com \
  --only-categories=performance \
  --output=html \
  --output-path=./report.html

# Mobile emulation (default)
lighthouse https://example.com --preset=desktop  # or default mobile

# With throttling disabled (tests actual connection speed)
lighthouse https://example.com --throttling-method=provided

Node.js API

const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');

async function runAudit(url) {
  const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
  const result = await lighthouse(url, {
    port: chrome.port,
    onlyCategories: ['performance'],
    output: 'json',
  });

  // Image-specific audit keys
  const imageKeys = [
    'uses-responsive-images', 'modern-image-formats',
    'uses-optimized-images', 'offscreen-images',
    'unsized-images', 'preload-lcp-image',
  ];

  for (const key of imageKeys) {
    const audit = result.lhr.audits[key];
    if (audit?.score !== null && audit?.score < 1) {
      console.log(`[FAIL] ${key}: ${audit.displayValue || 'needs attention'}`);
    }
  }

  await chrome.kill();
  return result;
}

runAudit('https://example.com');

Setting Up Lighthouse CI

Performance Budgets for Images

Create a lighthouse-budget.json to enforce image performance:

[
  {
    "path": "/*",
    "resourceSizes": [
      {
        "resourceType": "image",
        "budget": 500
      },
      {
        "resourceType": "total",
        "budget": 1500
      }
    ],
    "resourceCounts": [
      {
        "resourceType": "image",
        "budget": 25
      }
    ]
  },
  {
    "path": "/blog/*",
    "resourceSizes": [
      {
        "resourceType": "image",
        "budget": 800
      }
    ]
  }
]

Lighthouse CI Configuration

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: [
        'http://localhost:3000/',
        'http://localhost:3000/products/',
        'http://localhost:3000/blog/sample-post/',
      ],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'uses-responsive-images': ['warn', { minScore: 0.9 }],
        'modern-image-formats': ['error', { minScore: 0.9 }],
        'uses-optimized-images': ['error', { minScore: 0.9 }],
        'offscreen-images': ['warn', { minScore: 0.8 }],
        'unsized-images': ['error', { minScore: 1 }],
        'preload-lcp-image': ['warn', { minScore: 1 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        'largest-contentful-paint': ['warn', { maxNumericValue: 2500 }],
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

PageSpeed Insights vs Lighthouse

Key Differences

AspectLighthouse (DevTools/CLI)PageSpeed Insights
Data typeLab data (simulated)Lab + Field data (CrUX)
DeviceYour settingsMobile + Desktop
NetworkSimulated throttlingSimulated (lab) + Real (field)
ConsistencyVaries run to runMore stable (field data)
API accessNode.js APIREST API

When to Use Each

  • Lighthouse: Development, CI/CD, debugging specific pages
  • PageSpeed Insights: Production monitoring, real-user metrics, SEO assessment
  • CrUX (Chrome UX Report): Understanding actual user experience across pages

You can also use the PageSpeed Insights REST API to programmatically check image audit scores for any URL, which is useful for monitoring production pages without running a local Lighthouse instance.

Before/After Case Studies

Case Study 1: Blog with 15 Images Per Post

Before:

  • All images: 4000px JPEG at quality 95
  • No lazy loading
  • No width/height attributes
  • Performance score: 38
AuditStatusPotential Savings
Properly size imagesFAIL3.2 MB
Next-gen formatsFAIL1.8 MB
Efficiently encodeFAIL900 KB
Defer offscreenFAIL4.1 MB
Explicit dimensionsFAILCLS 0.32

Fixes applied:

  1. Resized images to max 1600px width
  2. Generated WebP variants, served via <picture>
  3. Re-encoded JPEGs at quality 82 with MozJPEG
  4. Added loading="lazy" to images 3-15
  5. Added width and height to all images
  6. Added fetchpriority="high" to hero image

After:

  • Performance score: 94
  • LCP: 1.8s (was 5.2s)
  • CLS: 0.02 (was 0.32)
  • Total image weight: 420 KB (was 5.9 MB)

Case Study 2: E-Commerce Product Page

Before:

  • 6 product images at 3000x3000 PNG
  • Background image loaded via CSS
  • No optimization
  • Performance score: 29

Fixes applied:

  1. Switched to Sirv for image hosting with auto-format
  2. Used URL-based resizing (?w=800 for main, ?w=200 for thumbnails)
  3. Preloaded the main product image
  4. Lazy-loaded gallery thumbnails
  5. Set explicit dimensions on all images

After:

  • Performance score: 91
  • LCP: 1.4s (was 6.8s)
  • CLS: 0 (was 0.18)
  • Total image weight: 280 KB (was 12.4 MB)

Case Study 3: Photography Portfolio

A portfolio with 40 full-resolution images on the homepage scored 12 in Performance. After implementing responsive images, converting to WebP/AVIF via Sirv, lazy-loading below the first row, using LQIP placeholders, setting dimensions, and batch-optimizing 200+ images with Sirv AI Studio, the score jumped to 88 with LCP dropping from 12.4s to 2.1s and initial image weight from 18.6 MB to 380 KB.

Image Performance Budget

Setting Budgets

A performance budget defines the maximum resources a page can load. Set image-specific limits:

Page TypeTotal Image BudgetPer-Image MaxMax Image Count
Homepage500 KB150 KB15
Blog post800 KB200 KB20
Product page600 KB250 KB10
Gallery400 KB (initial)80 KB/thumb50 (lazy loaded)

Monitoring Image Budgets in Production

Use the web-vitals library to track real-user image performance and identify regressions:

import { onLCP, onCLS } from 'web-vitals';

onLCP((metric) => {
  const lcpEntry = metric.entries[metric.entries.length - 1];
  if (lcpEntry.element?.tagName === 'IMG') {
    navigator.sendBeacon('/api/metrics', JSON.stringify({
      metric: 'lcp',
      value: metric.value,
      element: 'image',
      url: lcpEntry.element.src,
    }));
  }
});

Combine this with your Lighthouse CI assertions to catch image performance issues before they reach production, and monitor real-user impact after deployment.

Quick Reference: All Image Audits

Lighthouse AuditCategoryFix Summary
Properly size imagesOpportunityUse responsive srcset and sizes, or a CDN with URL resizing
Serve images in next-gen formatsOpportunityUse <picture> with WebP/AVIF sources, or a CDN with auto-format
Efficiently encode imagesOpportunityRe-compress with MozJPEG q80-85, strip metadata
Defer offscreen imagesOpportunityAdd loading="lazy" to below-fold images
Image elements do not have explicit width and heightDiagnosticAdd width and height attributes to every <img>
Preload Largest Contentful Paint imageOpportunityAdd <link rel="preload"> or fetchpriority="high"
Avoid enormous network payloadsDiagnosticSet image budgets, compress, and lazy load
Uses passive listenersDiagnosticReplace scroll listeners with Intersection Observer or native lazy loading

Every image audit in Lighthouse has a concrete fix. Start with the highest-savings opportunities (usually “Properly size images” and “Serve next-gen formats”), then work through the rest. Using an image CDN like Sirv addresses most of these audits automatically — proper sizing via URL parameters, automatic WebP/AVIF delivery, and global edge caching for fast delivery.

Related Resources

Format References

Ready to optimize your images?

Sirv automatically optimizes, resizes, and converts your images. Try it free.

Start Free Trial