Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.
ImagesHigh

Optimise images for faster loading

All images are compressed and metadata-stripped before deployment, removing unnecessary bytes without visible quality loss.

Utilities
Quick take
Typical fix time 20 min
  • Strip EXIF metadata from all JPEG/WebP images before deployment—it adds unnecessary bytes
  • JPEG: quality 80 with `progressive: true` (mozjpeg); PNG: run pngquant at quality 65-80
  • SVG: run through SVGO to remove editor artefacts and reduce file size
  • Lighthouse flags images reducible by 4KB+ as 'Efficiently encode images' opportunities
Why it matters: Unoptimised images are the most prevalent cause of page bloat. A DSLR-quality JPEG with EXIF data can be 8MB; the same image optimised for web delivery is under 200KB with no visible quality difference. Lighthouse's 'Efficiently encode images' audit reports potential savings that, when fixed, directly improve LCP and reduce bandwidth costs.

Rule Details

Image optimisation compresses image data and strips unnecessary metadata before serving files to users. It is distinct from format selection (JPEG vs WebP) and responsive sizing—each reduces file size in different ways.

Code Example

// Sharp (Node.js) — recommended for server-side processing
import sharp from 'sharp'
 
await sharp('input.jpg')
  .jpeg({
    quality: 80,           // 80 is the sweet spot: minimal visible loss, 40-60% size reduction
    progressive: true,     // Progressive JPEG shows low-res preview while loading
    mozjpeg: true,         // Use mozjpeg encoder for better compression at same quality
  })
  .toFile('output.jpg')
  // Note: sharp strips metadata by default

Why It Matters

Unoptimised images are the most prevalent cause of page bloat. A DSLR-quality JPEG with EXIF data can be 8MB; the same image optimised for web delivery is under 200KB with no visible quality difference. Lighthouse's 'Efficiently encode images' audit reports potential savings that, when fixed, directly improve LCP and reduce bandwidth costs.

What Optimisation Removes

  • EXIF metadata — GPS location, camera model, lens, timestamp embedded by cameras (can add 30-60KB per image)
  • IPTC/XMP metadata — editorial information added by photo editing software
  • ICC colour profiles — rarely needed for web delivery (a few KB each)
  • Redundant pixel data — recompression at lower quality removes high-frequency data invisible to the eye
  • SVG editor artefacts — Inkscape/Illustrator/Figma export adds comments, IDs, and inline styles that serve no purpose in production

Optimising PNG

# pngquant: lossy compression, 40-80% size reduction with minimal visible change
pngquant --quality=65-80 --output output.png input.png
 
# oxipng: lossless compression, 10-20% reduction with zero quality loss
oxipng -o 6 input.png
// Sharp PNG compression
await sharp('input.png')
  .png({
    compressionLevel: 9,  // 0-9, higher = smaller but slower
    effort: 10,           // 1-10, higher = slower but better compression
    palette: true,        // Enable palette quantisation for PNGs with few colours
    quality: 80,          // Quality when palette is true
  })
  .toFile('output.png')

Optimising SVG

# SVGO: removes editor metadata, comments, redundant attributes
npx svgo input.svg -o output.svg
 
# Or process all SVGs in a directory
npx svgo --folder public/icons
// SVGO configuration (svgo.config.js)
module.exports = {
  plugins: [
    'removeDoctype',
    'removeXMLProcInst',
    'removeComments',
    'removeMetadata',
    'removeEditorsNSData',
    'cleanupAttrs',
    'mergeStyles',
    'inlineStyles',
    'minifyStyles',
    'cleanupIds',
    'removeUselessDefs',
    'cleanupNumericValues',
    'convertColors',
    'removeUnknownsAndDefaults',
    'removeNonInheritableGroupAttrs',
    'removeUselessStrokeAndFill',
    'removeViewBox',         // Set to false if you need responsive SVGs
    'cleanupEnableBackground',
    'convertShapeToPath',
    'convertEllipseToCircle',
    'moveElemsAttrsToGroup',
    'moveGroupAttrsToElems',
    'collapseGroups',
    'convertPathData',
    'convertTransform',
    'removeEmptyAttrs',
    'removeEmptyContainers',
    'mergePaths',
    'removeUnusedNS',
    'sortDefsChildren',
    'removeTitle',
    'removeDesc',
  ],
}

Build Pipeline Integration

// vite.config.js — automatic optimisation at build time
import { defineConfig } from 'vite'
import { viteImageOptimizer } from 'vite-plugin-image-optimizer'
 
export default defineConfig({
  plugins: [
    viteImageOptimizer({
      jpg: { quality: 80, progressive: true },
      jpeg: { quality: 80, progressive: true },
      png: { quality: 80, compressionLevel: 9 },
      webp: { quality: 80, effort: 6 },
      avif: { quality: 60, effort: 6 },
      svg: {
        plugins: [
          { name: 'removeViewBox', active: false },
          { name: 'removeDimensions', active: true },
        ],
      },
    })
  ]
})

Automated CI Check

// scripts/check-image-optimisation.mjs
// Fail CI if any image could be reduced by more than 10KB
import { execSync } from 'child_process'
import { globSync } from 'glob'
 
const images = globSync('public/**/*.{jpg,jpeg,png,gif}')
let failed = false
 
for (const imgPath of images) {
  try {
    // Use identify (ImageMagick) to check for metadata
    const result = execSync(`identify -verbose "${imgPath}" 2>&1 | grep -i "exif\\|iptc\\|comment"`, {
      encoding: 'utf8',
      stdio: ['pipe', 'pipe', 'pipe'],
    })
    if (result.trim()) {
      console.warn(`⚠️  Metadata found in: ${imgPath}`)
      failed = true
    }
  } catch {
    // No metadata found — this is good
  }
}
 
if (failed) process.exit(1)

Verification

Automated Checks

  • Run Lighthouse — "Efficiently encode images" shows potential savings per image
  • Use Chrome DevTools → Network → filter by Img → check Transfer Size column for each image
  • Run exiftool -all= output.jpg after processing to confirm metadata was stripped

Manual Checks

  • Open each image in Squoosh — compare the original vs compressed side-by-side

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Audit all image assets in this project for optimisation issues: 1) Run Lighthouse and check the 'Efficiently encode images' audit—it flags images that could be reduced by 4KB or more. 2) Check if JPEG images use progressive encoding (better perceived performance). 3) Check if images retain EXIF/IPTC metadata (adds unnecessary bytes). 4) Check if PNG images could be quantised (pngquant) for smaller files. 5) Check if SVG files contain unnecessary editor metadata, comments, or redundant attributes. Report each image with its current size and estimated savings.

Fix

Auto-fix issues

For each unoptimised image: 1) JPEG: re-encode with mozjpeg at quality 80 with progressive=true. 2) PNG: run pngquant at quality 65-80 for lossy compression, or oxipng for lossless. 3) WebP: encode at quality 80. 4) AVIF: encode at quality 60 with effort 6. 5) SVG: run through SVGO to remove editor metadata, comments, and redundant attributes. 6) Strip EXIF metadata from all JPEG/WebP images. For each image, show the before/after file size and percentage reduction.

Explain

Learn more

Explain what image optimisation means technically. JPEG compression works by converting pixel data to frequency components (DCT) and quantising the high-frequency data—quality settings control how aggressively this quantisation occurs. PNG uses lossless DEFLATE compression, but colour quantisation (pngquant) can massively reduce file size with minimal visible change. EXIF metadata (GPS, camera model, timestamps) adds kilobytes to images with no benefit for web users. Progressive JPEG encoding allows the browser to show a low-resolution version immediately while the full image loads, improving perceived performance.

Review

Code review

Review image assets, markup, and delivery configuration related to Optimise images for faster loading. Flag exact files or components where format choice, sizing, or loading behavior violates the rule, and describe how to confirm the fix in DevTools.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

Squooshsquoosh.appTool
ImageOptimimageoptim.comTool
Sharpsharp.pixelplumbing.comTool
SVGOgithub.comTool
Lighthousedeveloper.chrome.comTool

Rules that often go hand-in-hand with this one.

Use modern image formats (WebP, AVIF)

Images are served in modern formats (WebP or AVIF) instead of legacy JPEG/PNG where browser support allows, reducing file size without visible quality loss.

Images
Serve images at the correct display size

Images are not significantly larger than their display dimensions—serving a 2000px image for a 400px container wastes bandwidth and hurts LCP.

Images
Keep image file sizes within recommended limits

Individual image files are compressed to reasonable sizes to avoid wasted bandwidth and slow load times, especially on mobile networks.

Images
Manage inline SVG size and complexity

Large or complex SVGs inlined in HTML are extracted to external files or components, preventing them from bloating the HTML document and blocking parsing.

Images

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385