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

Compress images without quality loss

All images are compressed without significant quality loss to reduce file sizes.

Utilities
Quick take
Typical fix time 15 min
  • Compress JPEG to 70-85% quality (often visually identical)
  • Use lossy compression for photos, lossless for graphics
  • Automate compression in build process
  • Can reduce file sizes by 60-80% without visible quality loss
Why it matters: Uncompressed images are often 5-10x larger than needed—proper compression dramatically reduces page weight and load times with minimal visual impact.

Rule Details

Image compression reduces file sizes significantly with minimal visual quality loss.

Code Example

# ImageMagick - JPEG compression
convert input.jpg -quality 80 output.jpg
 
# Sharp CLI - WebP conversion with quality
npx sharp-cli input.jpg -o output.webp --quality 80
 
# pngquant - lossy PNG compression
pngquant --quality=65-80 input.png
 
# svgo - SVG optimization
npx svgo input.svg -o output.svg

Why It Matters

Uncompressed images are often 5-10x larger than needed—proper compression dramatically reduces page weight and load times with minimal visual impact.

Quality Guidelines

Image TypeFormatRecommended Quality
PhotosJPEG/WebP70-85%
Product imagesJPEG/WebP80-90%
ScreenshotsPNG/WebPLossless or 90%+
Icons/logosSVG/PNGLossless
ThumbnailsJPEG/WebP60-75%

Sharp (Node.js) Automation

const sharp = require('sharp')
 
async function compressImage(inputPath, outputPath, options = {}) {
  const { quality = 80, format = 'webp' } = options
 
  await sharp(inputPath)
    [format]({ quality })
    .toFile(outputPath)
}
 
// Batch processing
async function compressDirectory(inputDir, outputDir) {
  const files = fs.readdirSync(inputDir)
 
  for (const file of files) {
    if (/\.(jpg|jpeg|png)$/i.test(file)) {
      await compressImage(
        path.join(inputDir, file),
        path.join(outputDir, file.replace(/\.[^.]+$/, '.webp'))
      )
    }
  }
}

Webpack Configuration

const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')
 
module.exports = {
  optimization: {
    minimizer: [
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.sharpMinify,
          options: {
            encodeOptions: {
              jpeg: { quality: 80 },
              webp: { quality: 80 },
              png: { quality: 80 },
              avif: { quality: 60 }
            }
          }
        }
      })
    ]
  }
}

Vite Configuration

// vite.config.js
import viteImagemin from 'vite-plugin-imagemin'
 
export default {
  plugins: [
    viteImagemin({
      gifsicle: { optimizationLevel: 3 },
      optipng: { optimizationLevel: 5 },
      mozjpeg: { quality: 80 },
      webp: { quality: 80 },
      svgo: {
        plugins: [
          { name: 'removeViewBox', active: false },
          { name: 'removeEmptyAttrs', active: false }
        ]
      }
    })
  ]
}

React Build Script

// scripts/optimize-images.js
const sharp = require('sharp')
const glob = require('glob')
const path = require('path')
 
const INPUT_DIR = 'public/images'
const OUTPUT_DIR = 'public/images/optimized'
 
async function optimizeImages() {
  const files = glob.sync(`${INPUT_DIR}/**/*.{jpg,jpeg,png}`)
 
  for (const file of files) {
    const relativePath = path.relative(INPUT_DIR, file)
    const outputPath = path.join(OUTPUT_DIR, relativePath)
 
    // Create WebP version
    await sharp(file)
      .webp({ quality: 80 })
      .toFile(outputPath.replace(/\.[^.]+$/, '.webp'))
 
    // Create optimized original format
    await sharp(file)
      .jpeg({ quality: 80, progressive: true })
      .toFile(outputPath)
 
    console.log(`Optimized: ${relativePath}`)
  }
}
 
optimizeImages()

Online Tools Comparison

ToolBest ForCompression Type
TinyPNGPNG/JPEGLossy
SquooshAll formatsLossy/lossless
ImageOptimMac batchLossless
SVGOMGSVGLossless
Compressor.ioQuick onlineLossy

Measuring Compression Results

// Compare file sizes
const fs = require('fs')
 
function getCompressionStats(originalPath, compressedPath) {
  const originalSize = fs.statSync(originalPath).size
  const compressedSize = fs.statSync(compressedPath).size
  const savings = ((originalSize - compressedSize) / originalSize * 100).toFixed(1)
 
  return {
    original: `${(originalSize / 1024).toFixed(1)} KB`,
    compressed: `${(compressedSize / 1024).toFixed(1)} KB`,
    savings: `${savings}%`
  }
}

GitHub Actions Automation

# .github/workflows/optimize-images.yml
name: Optimize Images
 
on:
  push:
    paths:
      - 'public/images/**'
 
jobs:
  optimize:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: calibreapp/image-actions@main
        with:
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          jpegQuality: '80'
          pngQuality: '80'
          webpQuality: '80'

Standards

  • Use these references as the standard for the final image format, delivery, accessibility, and rendering behavior.
  • Check the implementation against MDN: Responsive images before treating the rule as satisfied.
  • Check the implementation against web.dev: Image performance before treating the rule as satisfied.

Verification

Automated Checks

  • Check file sizes in DevTools Network tab
  • Run Lighthouse—check "Efficiently encode images" audit
  • Test on different screen sizes and densities

Manual Checks

  • Compare original and compressed images side-by-side
  • Verify compression doesn't create visible artifacts

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

Analyze if images are properly compressed without significant quality loss.

Fix

Auto-fix issues

Compress images using tools like ImageOptim, TinyPNG, or automated build processes.

Explain

Learn more

Explain how proper compression can reduce image sizes by 60-80% with minimal quality impact.

Review

Code review

Review image assets, markup, and delivery configuration related to Compress images without quality loss. 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.

Squoosh
squoosh.appTool

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

Optimise images for faster loading

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

Images
Optimize all images for web

Images are optimized with appropriate formats, compression, and modern techniques.

Images
Optimize SVG files

SVG files are optimized with SVGO to remove unnecessary metadata and reduce size.

Images
Fix broken images

No images return 404 errors or display broken-image icons to users.

Images

Was this rule helpful?

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

Loading feedback...
0 / 385