Image compression is a discipline most engineers learn through trial and error, which is why most production sites ship images that are larger than they should be. The HTTP Archive's 2024 Web Almanac reports that the median web page sends 980 KB of images, and that the median image file is 27 percent larger than an optimal encoder of the same format would produce. The total wasted bandwidth across the public web from suboptimal image compression is measured in petabytes per day.

Mastering image compression is therefore a high-leverage skill. It improves Core Web Vitals, lowers CDN bills, accelerates page loads on slow networks, and (because Google's ranking signals weight LCP heavily) helps SEO. This guide covers the encoders that matter, the perceptual metrics that measure quality honestly, the build-pipeline patterns that deliver consistent results, and the operational practices that prevent regressions.

The Mental Model: Quality Is a Distribution

A quality setting is not a guarantee. The same JPEG quality 85 setting on a portrait of a face produces different visible quality than on a screenshot of code. Compression artifacts are content-dependent, and the right approach is to think of image quality as a distribution over content types rather than a single number.

This distinction matters because it changes the right tool. A single quality knob assumes uniform content. A perceptual quality target (for example, "Butteraugli score below 1.5") adapts to the content automatically; the encoder finds the lowest bitrate that meets the target.

Modern encoders support both modes. Production pipelines typically use a fixed quality for predictability, then audit the output against perceptual metrics on samples to confirm the quality target is met across the full content distribution.

"Pixel difference is not perceptual difference. Two images with identical SSIM can look very different to a human; two images with identical Butteraugli scores tend not to." Jyrki Alakuijala, principal author of Butteraugli, Brotli, and JPEG XL

The Encoders That Matter

For each format, one or two encoders dominate quality benchmarks. The wrong encoder, even with the right quality setting, produces measurably worse output.

FormatBest EncoderNotes
JPEGmozjpeg5 to 15 percent smaller than libjpeg-turbo at equal quality
PNGoxipng with ZopfliLossless, multi-pass, parallel
PNG (lossy)pngquantQuantizes to indexed palette, large savings
WebPlibwebp via cwebpStandard reference encoder
AVIFlibavif (avifenc)Built on dav1d/aom, tunable speed-quality
JPEG XLlibjxl (cjxl)Fast, high quality, lossless JPEG transcode
SVGSVGOMulti-pass with selective plugins
The pattern across formats is the same: encoders improve over time, and the encoder that ships in your operating system or default image library is often years behind. Production pipelines should pin specific encoder versions and audit the output rather than trust system defaults.

JPEG: The mozjpeg Default

Mozilla's mozjpeg fork of libjpeg-turbo has been the highest-quality JPEG encoder since 2014. The improvements come from trellis quantization, jpegcrush-style scan optimization, and progressive coding tuning. The output is bit-compatible with every JPEG decoder; only the encoder is different.

Recommended invocation for typical photographic content:

# mozjpeg with progressive coding and quantization optimization
cjpeg -quality 82 \
      -progressive \
      -optimize \
      -dc-scan-opt 2 \
      -quant-table 3 \
      -outfile photo.jpg photo.png

A few details deserve emphasis. Progressive JPEGs render incrementally, which improves perceived load time. Quality 82 hits a near-optimal point for typical content; the marginal byte cost above 85 outpaces the perceptual gain. Quant-table 3 (the "imagemagick" table) handles photographic content slightly better than the default IJG tables.

For batch processing, parallelize across files rather than within a file:

# Parallel mozjpeg across a directory
find originals/ -name '*.png' -print0 | \
  xargs -0 -n 1 -P "$(nproc)" -I {} \
  sh -c 'cjpeg -quality 82 -progressive -optimize \
              -outfile "out/$(basename "{}" .png).jpg" "{}"'

PNG: Lossless First, Lossy Where Acceptable

PNG is the canonical lossless format for screenshots, UI assets, and any image where pixel-perfect output matters. The default encoders that ship in image libraries (libpng, Pillow's PNG writer) produce PNGs that are 10 to 30 percent larger than necessary. Two tools fix this without quality loss.

oxipng runs DEFLATE optimization passes and rebuilds chunks. It supports Zopfli, a slow-but-thorough compressor that produces the smallest possible DEFLATE output.

ECT (Efficient Compression Tool) combines PNG, JPEG, and ZIP optimization with even more aggressive settings.

# oxipng with Zopfli for archive-grade PNG
oxipng --opt max --strip safe --zopfli original.png

# ECT for maximum lossless reduction
ect -9 --strip --strict original.png

When lossless is not required (and for many web use cases, it is not), pngquant produces dramatically smaller files by quantizing to an indexed palette:

# Lossy PNG via palette quantization
pngquant --quality 70-90 \
         --strip \
         --speed 1 \
         --output photo.q.png photo.png

Quantized PNGs are still PNGs; they decode in every browser without fallback. The visual difference on photographic content is subtle but present. On UI screenshots and illustrations, quantization is often invisible.

WebP and AVIF: Modern Format Discipline

The bulk of compression savings on the modern web comes from format choice. AVIF and WebP are smaller than JPEG at equivalent perceptual quality, often substantially so.

# WebP, lossy
cwebp -q 80 -m 6 -mt photo.png -o photo.webp

# WebP, lossless (for screenshots and line art)
cwebp -lossless -m 6 -z 9 screenshot.png -o screenshot.webp

# AVIF, web tier
avifenc --speed 6 --qcolor 50 --qalpha 60 \
        --yuv 420 --jobs all photo.png photo.avif

# AVIF, pro tier with full chroma
avifenc --speed 4 --min 0 --max 30 \
        --yuv 444 --depth 10 photo.png photo.avif

The speed parameter for AVIF deserves attention. Speed 6 is reasonable for production; speed 4 produces 5 to 10 percent smaller files at three to four times the encode cost. Pipelines with idle CPU should run speed 4. Pipelines with user-uploaded images need speed 6 or higher to keep latency reasonable.

EncoderSettingEncode time (1080p photo)File size
mozjpegq=82 progressive0.4 s240 KB
cwebpq=80 m=60.6 s175 KB
avifencspeed=6 q=501.2 s145 KB
avifencspeed=4 q=504.5 s135 KB
cjxlq=80 effort=71.0 s165 KB
These numbers, measured on a representative 1080p portrait, are illustrative. Real-world workloads vary; the right approach is to benchmark on your own content distribution.

Perceptual Quality Metrics

Pixel-level metrics (PSNR, MSE) are largely useless for image quality. The human visual system is non-linear, color-sensitive, and weights certain regions (faces, edges) far more than others. Perceptual metrics close this gap.

Butteraugli. Developed at Google. Models the spatial frequency response of human vision. Produces a single score where lower is better; scores below 1.5 are typically imperceptible.

SSIMULACRA2. A modern improvement on the original SSIM. Scores correlate well with subjective quality assessments and is fast enough for batch processing.

DSSIM (structural dissimilarity). Used internally by Cloudinary and similar services. Quick to compute and reasonably reliable.

# Butteraugli (from libjxl)
butteraugli original.png compressed.jpg

# SSIMULACRA2
ssimulacra2 original.png compressed.jpg

# DSSIM
dssim original.png compressed.jpg

A practical workflow: pick a quality target (for example, Butteraugli below 2.0), encode at multiple quality levels, choose the smallest file that meets the target. This automates the quality-vs-size trade-off across heterogeneous content.

"Quality metrics that disagree with humans are not quality metrics; they are math. Use the ones that have been validated against subjective scoring." Patrice Rondao Alface, video coding researcher, IEEE Multimedia article on perceptual quality

Build Pipeline Patterns

A reliable image pipeline has three layers, each with a clear responsibility.

Layer 1: Source ingestion. Originals enter the system as TIFF, PNG, or high-quality JPEG. Strip personally identifying EXIF metadata, hash the file, and store the canonical original on durable storage.

Layer 2: Format generation. A build step produces all the variants the site will deliver: AVIF, WebP, JPEG, multiple resolutions of each. Run this once per source change, not per request.

Layer 3: Delivery. A CDN selects the right variant based on Accept headers and viewport size. The CDN does not re-encode; it serves precomputed variants from cache.

A representative build script using Sharp (a Node.js libvips wrapper):

import sharp from 'sharp';
import { glob } from 'glob';
import path from 'path';

const widths = [400, 800, 1200, 1600, 2400];
const formats = [
  { ext: 'avif', options: { quality: 50, effort: 6 } },
  { ext: 'webp', options: { quality: 80, effort: 6 } },
  { ext: 'jpg',  options: { quality: 82, mozjpeg: true } }
];

for (const file of await glob('originals/**/*.{png,jpg}')) {
  const base = path.basename(file, path.extname(file));
  for (const w of widths) {
    for (const f of formats) {
      await sharp(file)
        .resize({ width: w, withoutEnlargement: true })
        .toFormat(f.ext, f.options)
        .toFile(`dist/${base}-${w}.${f.ext}`);
    }
  }
}

Static site generators that run this kind of pipeline at build time produce predictable, cacheable output. Sites with heavy image inventories, including content-rich publications like the long-form pieces at strangeanimals.info or whats-your-iq.com, benefit substantially from this pattern.

CDN-Level Compression

CDN image transformation handles format negotiation, viewport sizing, and edge caching. The four major options are Cloudflare Polish, Akamai Image Manager, Imgix, and self-hosted libvips/imgproxy. All four implement the same basic contract: a URL parameter or Accept header negotiates format, and the CDN serves the appropriate variant from cache.

The right defaults for CDN configuration:

  • AVIF for browsers that accept it.
  • WebP for browsers that accept WebP but not AVIF.
  • JPEG for everything else.
  • DPR-aware delivery (1x, 2x, 3x variants generated automatically).
  • Long cache lifetime on the immutable variants (Cache-Control: public, max-age=31536000, immutable).

Cloudflare's cf-polish=lossy is the simplest configuration; it does the right thing for most sites without further tuning. Self-hosted options provide more control but require operational investment.

Avoiding Double-Compression

The single most damaging image compression anti-pattern is re-encoding a previously compressed file. Each lossy encode introduces artifacts; a re-encode preserves the existing artifacts and adds new ones. This compounds.

The right discipline:

  1. Store the lossless original (or the highest-quality version available) as the canonical source.
  2. Generate all distribution formats from the canonical source in a single step.
  3. Never re-encode a distribution copy.

When user-uploaded content arrives as a JPEG, store that JPEG as the canonical source and treat any further encoding as compounding. JPEG XL's lossless transcode of an existing JPEG is the only operation that avoids the compounding penalty; every other format conversion of an already-compressed image will degrade quality.

Performance Measurement

Compression work is only valuable if it shows up in user-visible metrics. The metrics that matter:

Largest Contentful Paint (LCP). The hero image is usually the LCP candidate. Compression directly affects LCP time.

Total page bytes. Image compression usually moves this metric the most.

Image bytes per element. A more diagnostic version of total bytes; identifies outlier images.

Cumulative Layout Shift (CLS). Indirectly affected by compression: if dimensions are missing, layout shifts.

The Chrome User Experience Report (CrUX) provides field data; Lighthouse and PageSpeed Insights provide lab data. Track both. Field data captures real users; lab data is reproducible enough to debug regressions.

A simple Node.js script that audits an image directory against a budget:

import { readdirSync, statSync } from 'fs';
import path from 'path';

const BUDGET = {
  '.jpg': 250 * 1024,
  '.webp': 175 * 1024,
  '.avif': 150 * 1024,
  '.png': 200 * 1024
};

let violations = 0;
for (const f of readdirSync('dist', { recursive: true })) {
  const ext = path.extname(f).toLowerCase();
  if (!(ext in BUDGET)) continue;
  const size = statSync(path.join('dist', f)).size;
  if (size > BUDGET[ext]) {
    console.log(`OVER BUDGET: ${f} ${size} > ${BUDGET[ext]}`);
    violations++;
  }
}
process.exit(violations === 0 ? 0 : 1);

Run this in CI. A failed build catches compression regressions before they ship.

Operational Hygiene

Compression at scale runs into edge cases that benchmarks rarely capture.

Color profiles. Strip irrelevant ICC profiles, but keep wide-gamut profiles when the source is wide-gamut and the target format supports color management. Mishandling profiles is one of the most common causes of "AVIF looks washed out" complaints.

Animated content. Animated WebP and AVIF are not always smaller than a video element with H.264 or AV1. Benchmark.

Transparency. Alpha channels increase file size meaningfully. Use alpha only when the design requires it.

Mixed content. Photos overlaid with text (banners with copy) compress poorly under lossy formats. Either render the text after compression in CSS, or accept the larger file.

Following these defaults across a content pipeline produces consistent, near-optimal output. Over time the discipline shows up in the CDN bill, the Core Web Vitals dashboard, and the user-facing performance the site delivers. For deeper format-specific guidance, the essential guide to choosing the right image format and SVG vs PNG vs JPG articles on this site cover related decisions.

Common Failure Modes in Production

Five recurring failures in production image compression deserve direct attention.

Encoder version drift. The mozjpeg or libavif version that ran in development differs from the one in production CI, and output quality differs subtly. Solution: pin encoder versions in lockfiles, hash output samples in CI.

Compressing thumbnails with the same settings as full-size images. Thumbnails benefit from more aggressive compression because perceptual artifacts are less visible at small sizes. Solution: separate quality settings per output size.

Serving the same byte-stream to retina and non-retina displays. Without DPR-aware delivery, retina users get the right resolution and non-retina users overpay. Solution: srcset with width descriptors, sizes attribute, and CDN-side density negotiation.

Cache busting on every deploy. Long-cached images get re-fetched whenever the URL changes. Solution: hash filenames so unchanged content keeps its URL across deploys.

Forgetting to compress on the upload path. User-uploaded images bypass the build pipeline. Solution: compress on upload server-side, do not trust the client.

Each failure costs measurable money in CDN bills, page weight, or Core Web Vitals scores. Each fix is mechanical once the failure is named.

Closing Discipline

Image compression rewards consistency over cleverness. The encoder choices, quality settings, and pipeline patterns described here are not novel; they are the same patterns the major web performance teams have converged on. The competitive advantage is in actually applying them, end to end, every time. Sites that do this ship pages that load fast on slow networks, score well on Core Web Vitals, and pay less to CDNs than competitors with similar content. The discipline is small. The compounded benefit is large.

References

  1. HTTP Archive, Web Almanac 2024, Media chapter. https://almanac.httparchive.org/en/2024/media
  2. Wang, Z. and Bovik, A. C. Image quality assessment: from error visibility to structural similarity. IEEE Transactions on Image Processing, 13(4), 2004. https://doi.org/10.1109/TIP.2003.819861
  3. Alakuijala, J. et al. Guetzli: perceptually guided JPEG encoder. arXiv preprint, 2017. https://arxiv.org/abs/1703.04421
  4. Mozilla, mozjpeg Project Documentation. https://github.com/mozilla/mozjpeg/blob/master/README.md
  5. AOMedia, AV1 Image File Format (AVIF) Specification 1.1.0. https://aomediacodec.github.io/av1-avif/
  6. Google, libwebp Compression Study. https://developers.google.com/speed/webp/docs/webp_study
  7. Lighthouse Performance Auditing Methodology, Google Chrome Developers. https://developer.chrome.com/docs/lighthouse/performance/
  8. ITU-R BT.500-14, Methodology for the subjective assessment of the quality of television pictures, 2019. https://www.itu.int/rec/R-REC-BT.500