MiniPxMiniPx
Blog
Images never leave your browser
← Blog

Image Compression for Developers

By Gaurav Bhowmick··12 min read

Images account for roughly 50% of the average web page's weight. Compress them well and your site loads faster, your hosting bill drops, and your Core Web Vitals improve. Compress them poorly and users stare at blank rectangles on 3G connections. This guide covers what actually matters for developers.

Format selection in 2026

The format landscape has stabilised. AVIF has the best compression ratio and is supported by all major browsers. WebP is the safe middle ground — slightly less efficient than AVIF but with broader legacy support. JPEG remains the universal fallback. PNG is for lossless needs only.

The practical approach is to generate multiple formats and let the browser choose. The HTML picture element handles this cleanly:

<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="..." width="1200" height="630"
       loading="lazy" decoding="async">
</picture>

The browser evaluates sources top-to-bottom and picks the first one it supports. No JavaScript, no user-agent sniffing, no content negotiation headers. It just works.

Quality settings that actually matter

Quality is not linear. JPEG quality 100 vs 95 produces a file 2-3x larger with a difference invisible to the human eye. Quality 85 vs 80 saves another 20-30% with artifacts only visible at pixel-level zoom. Below 70, compression artifacts become noticeable in normal viewing.

For AVIF, the quality scale is different. AVIF quality 60 is roughly equivalent to JPEG quality 85 in visual terms, but produces a file 30-40% smaller. When setting up your compression pipeline, do not transfer JPEG quality numbers to AVIF — test visually and calibrate per format.

One quality setting does not fit all images. A photograph with smooth gradients compresses differently than a screenshot with sharp text. If your build pipeline supports per-image configuration, use it. If not, quality 80 for JPEG and quality 60 for AVIF are reliable defaults that work well across image types.

Responsive images: stop serving 2000px to phones

A 2000px-wide hero image is 400KB as JPEG. The same image at 800px is 80KB. On a 375px mobile screen, the 2000px version looks identical to the 800px version — but costs 5x more bandwidth and takes 5x longer to decode.

Generate 3-4 sizes during your build: 400w, 800w, 1200w, and the original size. Use srcset to let the browser pick the right one:

<img srcset="hero-400.jpg 400w,
             hero-800.jpg 800w,
             hero-1200.jpg 1200w,
             hero-2000.jpg 2000w"
     sizes="(max-width: 600px) 100vw,
            (max-width: 1200px) 80vw,
            1200px"
     src="hero-1200.jpg"
     alt="..." width="2000" height="1050"
     loading="lazy" decoding="async">

The sizes attribute tells the browser how wide the image will be at each viewport width, so it can calculate which source to download before the CSS has loaded. Without sizes, the browser defaults to assuming the image is full-viewport width, which often means downloading an oversized version.

Build-time compression with sharp

sharp is the most widely-used Node.js image processing library. It wraps libvips, which is significantly faster than ImageMagick for batch operations. A basic compression script processes hundreds of images in seconds:

const sharp = require('sharp');
const glob = require('glob');

const files = glob.sync('src/images/**/*.{jpg,png}');

for (const file of files) {
  // Generate AVIF
  await sharp(file)
    .resize(1200, null, { withoutEnlargement: true })
    .avif({ quality: 60 })
    .toFile(file.replace(/\.(jpg|png)$/, '.avif'));

  // Generate WebP
  await sharp(file)
    .resize(1200, null, { withoutEnlargement: true })
    .webp({ quality: 80 })
    .toFile(file.replace(/\.(jpg|png)$/, '.webp'));

  // Optimise original JPEG
  await sharp(file)
    .resize(1200, null, { withoutEnlargement: true })
    .jpeg({ quality: 85, mozjpeg: true })
    .toFile(file.replace(/\.(jpg|png)$/, '-opt.jpg'));
}

The mozjpeg option enables Mozilla's JPEG encoder, which produces 5-10% smaller files than the default libjpeg. The withoutEnlargement flag prevents upscaling images that are already smaller than 1200px.

Client-side compression with Canvas API

When you need to compress images in the browser — before uploading to a server, for instance — the Canvas API handles it natively. No libraries needed, though they can simplify the code:

async function compressImage(file, maxWidth = 1200, quality = 0.8) {
  const img = new Image();
  const url = URL.createObjectURL(file);
  await new Promise(r => { img.onload = r; img.src = url; });

  const scale = Math.min(1, maxWidth / img.width);
  const canvas = document.createElement('canvas');
  canvas.width = img.width * scale;
  canvas.height = img.height * scale;
  canvas.getContext('2d').drawImage(img, 0, 0,
    canvas.width, canvas.height);

  URL.revokeObjectURL(url);
  return new Promise(resolve =>
    canvas.toBlob(resolve, 'image/jpeg', quality));
}

This approach resizes and recompresses in one step. The quality parameter ranges from 0 to 1 (0.8 = 80%). For AVIF output, use 'image/avif' as the MIME type — supported in Chrome 96+ and Firefox 113+. Check support with canvas.toBlob() in a try-catch before offering AVIF to users.

CI/CD integration

Automating image compression in your deployment pipeline ensures that no uncompressed image ever reaches production. Add the sharp script above as a build step, or use a dedicated tool like imagemin-cli for simpler setups.

In GitHub Actions, add a step after checkout that installs sharp and runs your compression script. Cache the node_modules directory to keep build times fast. The compressed images get committed to the build output (not to your source repo — keep originals in source).

For teams, consider adding an image size check as a PR gate. A simple script that flags any new image over 500KB prevents the "someone added a 4MB hero image" problem that haunts every long-running project.

Framework-specific approaches

Next.js: The built-in Image component handles resizing and format conversion at request time. It generates WebP and AVIF on-the-fly, caches the results, and serves the right format based on the Accept header. For static exports, use next-image-export-optimizer to pre-generate at build time.

Astro: The astro:assets integration uses sharp under the hood. Import images in your components and Astro generates multiple formats and sizes automatically.

Static sites (Hugo, 11ty, Jekyll): Use a build-time compression script with sharp or imagemin. These generators do not process images natively, so you handle it in your build pipeline.

Performance impact: the numbers

Uncompressed images are the single largest drag on web performance. Here is what proper image compression typically achieves:

  • LCP improvement: 30-60% faster when hero images are properly compressed and sized
  • Page weight reduction: 40-70% smaller total page size
  • Bandwidth savings: For a site serving 1M pages/month with 5 images each, switching from uncompressed JPEG to AVIF saves roughly 2-3 TB/month of transfer
  • Mobile impact: On a 3G connection, a 500KB image takes ~5 seconds to load; a properly compressed 100KB version takes ~1 second

The checklist

For any production website, make sure you are doing all of these:

  1. Serve AVIF with WebP and JPEG fallbacks using the picture element
  2. Generate responsive sizes (400w, 800w, 1200w minimum) and use srcset
  3. Set explicit width and height attributes to prevent layout shift (CLS)
  4. Use loading="lazy" for below-fold images, loading="eager" for the hero
  5. Add fetchpriority="high" to the LCP image
  6. Strip unnecessary EXIF metadata (location data, camera info) for privacy
  7. Automate compression in your build pipeline so nothing ships unoptimised

Images are the lowest-hanging fruit in web performance. A few hours of setup in your build pipeline pays off on every page load, for every user, forever.

Frequently asked questions

What image format should I use for web development in 2026?
Use AVIF as your primary format with WebP fallback and JPEG as the final fallback. The HTML picture element makes this straightforward — serve AVIF to the 95%+ of browsers that support it, WebP to the small percentage that support WebP but not AVIF, and JPEG to everything else. For graphics with transparency, AVIF or WebP replace PNG with much smaller files.
What JPEG quality setting should I use?
Quality 80-85 is the sweet spot for most web images. Below 80, compression artifacts become visible on close inspection. Above 85, file size increases significantly with minimal visual improvement. Quality 60-70 is acceptable for thumbnails and preview images where users will click through to a full-size version.
Should I compress images at build time or serve them through a CDN?
Both, if possible. Build-time compression gives you full control over quality settings and eliminates processing latency. An image CDN adds on-the-fly resizing and format negotiation for different devices and browsers. Build-time handles the heavy lifting; CDN handles the edge cases. If you can only pick one, build-time compression is cheaper and more predictable.
How do I automate image compression in CI/CD?
Add sharp or imagemin to your build pipeline. For Next.js, the built-in Image component handles optimisation at request time. For static sites, run a compression script as a build step — iterate over image directories, compress each file, and output to the build folder. GitHub Actions can run this on every push, and the compressed images get deployed automatically.
What is the Canvas API approach to client-side compression?
The browser Canvas API can compress images by drawing them onto a canvas element and exporting with canvas.toBlob() or canvas.toDataURL(). You control the output format (JPEG, WebP, AVIF in supported browsers) and quality parameter (0-1). This is how MiniPx and similar browser-based tools work — no server upload required.
How much does image size affect Core Web Vitals?
Images are the largest contributor to Largest Contentful Paint (LCP) on most pages. An uncompressed hero image can add 2-5 seconds to LCP on mobile connections. Properly compressed and sized images typically reduce LCP by 30-60%. Google explicitly recommends image optimisation as one of the highest-impact performance improvements.
Should I use responsive images with srcset?
Yes. Serving a 2000px image to a 375px mobile screen wastes bandwidth and slows page load. Use srcset to offer multiple sizes and let the browser pick the appropriate one. Generate 3-4 sizes (e.g., 400w, 800w, 1200w, 2000w) during your build process. The sizes attribute tells the browser how wide the image will display at each breakpoint.

Related tools

Compress JPEGCompress PNGCompress AVIFCompress for WebImage Optimizer

More from the blog

Optimize Images for Core Web Vitals — Complete GuideClient-Side Image Compression with JavaScriptHow Browsers Compress Images (Canvas API Explained)Automate Image Compression in CI/CD
🔧
Try MiniPx — free, no signup

Compress, convert, and resize images in your browser. Nothing gets uploaded.

Open MiniPx →

All Image Tools

Compress

Convert

Resize

Compress to Size

Compress for Platform

Tools & Utilities

By Device

Compare