Optimise images for faster loading
All images are compressed and metadata-stripped before deployment, removing unnecessary bytes without visible quality loss.
- 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
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 defaultWhy 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.jpgafter 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.