All Tools / Blog / How to Compress Images for the Web Without Losing Quality

How to Compress Images for the Web Without Losing Quality

5 min read

Large images are the single most common cause of slow web pages. A 4 MB photo from a phone camera, displayed at 800 px wide on a blog post, delivers the same visual result as a 60 KB optimized version — but takes 60× longer to download on a mobile connection.

This guide covers how to compress images effectively: choosing the right format, picking quality settings, and automating compression for production.

Lossy vs lossless compression

Lossy compression discards pixel data that the human eye is unlikely to notice — color variations in gradients, high-frequency noise in out-of-focus areas. JPEG and WebP use lossy compression. You control the trade-off with a quality parameter (0–100).

Lossless compression reorganizes data without discarding any of it. PNG uses lossless compression. You can run a PNG through a lossless optimizer like oxipng and get a smaller file with zero quality loss.

Most web images benefit from lossy compression. The exceptions are logos, screenshots with text, and anything where pixelation would be obvious.

Choosing the right format

Format Best for Notes
WebP Photos, UI screenshots ~25–35% smaller than JPEG at the same quality; supported in all modern browsers
JPEG Photos Universal support; lossy; no transparency
PNG Logos, icons, screenshots with text Lossless; supports transparency; larger than WebP
AVIF Photos 50% smaller than JPEG; slower to encode; browser support still catching up
SVG Icons, logos, charts Resolution-independent; tiny file size for vector art

Default rule: Convert photos and UI screenshots to WebP. Keep transparent images as PNG (or WebP with alpha). Use SVG for any vector art.

Quality settings that work

For JPEG and WebP, quality 75–85 is the standard web target. Below 70 you start to see visible artifacts in photos; above 85 you're saving data that makes no visible difference.

Use case Quality
Thumbnail (≤ 200 px) 60–70
Blog image, product photo 75–80
Hero image, banner 80–85
Print-quality reference 90+

In the browser

For one-off images, paste or drag into the image compressor, adjust the quality slider, and download the compressed version. The tool runs entirely client-side — the image never leaves your device.

Command line: sharp (Node.js)

sharp is the fastest Node.js image processing library. It wraps libvips.

npm install sharp
const sharp = require('sharp');

// Convert JPEG to WebP at quality 80
sharp('input.jpg')
    .webp({ quality: 80 })
    .toFile('output.webp')
    .then(info => console.log(info));

// Resize and compress
sharp('hero.png')
    .resize(1200)          // resize to 1200px wide, maintain aspect ratio
    .webp({ quality: 82 })
    .toFile('hero.webp');

Batch process an entire directory:

const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

const inputDir = './images/original';
const outputDir = './images/compressed';

fs.readdirSync(inputDir)
    .filter(f => /\.(jpg|jpeg|png)$/i.test(f))
    .forEach(file => {
        sharp(path.join(inputDir, file))
            .webp({ quality: 80 })
            .toFile(path.join(outputDir, file.replace(/\.(jpg|jpeg|png)$/i, '.webp')));
    });

Command line: ImageMagick

ImageMagick is available on most Linux/macOS systems and handles almost every format.

# Install
brew install imagemagick    # macOS
sudo apt install imagemagick # Ubuntu

# JPEG compression at quality 80
convert input.jpg -quality 80 output.jpg

# Convert to WebP
convert input.jpg -quality 80 output.webp

# Resize to max 1200px wide, then compress
convert input.jpg -resize 1200x -quality 80 output.webp

# Batch convert all JPEGs in a folder to WebP
for f in *.jpg; do convert "$f" -quality 80 "${f%.jpg}.webp"; done

Command line: cwebp (Google's WebP encoder)

# Install
brew install webp          # macOS
sudo apt install webp      # Ubuntu

# Convert with quality 80
cwebp -q 80 input.jpg -o output.webp

# Check the result
webpinfo output.webp

Python: Pillow

from PIL import Image

# Open and compress as JPEG
with Image.open('input.png') as img:
    img.convert('RGB').save('output.jpg', 'JPEG', quality=80, optimize=True)

# Open and compress as WebP
with Image.open('input.jpg') as img:
    img.save('output.webp', 'WebP', quality=80)

Batch compress with progress:

from PIL import Image
from pathlib import Path

input_dir = Path('images/original')
output_dir = Path('images/compressed')
output_dir.mkdir(exist_ok=True)

for img_path in input_dir.glob('*.{jpg,jpeg,png}'):
    with Image.open(img_path) as img:
        out_path = output_dir / img_path.stem
        img.save(f'{out_path}.webp', 'WebP', quality=80)
        print(f'{img_path.name}: {img_path.stat().st_size // 1024}KB → {(out_path.with_suffix(".webp")).stat().st_size // 1024}KB')

Responsive images — don't just compress, resize

Compressing a 3000 px wide image to quality 80 still gives you a large file. The right approach for web is to serve different sizes based on the device.

<img
  srcset="
    hero-400.webp   400w,
    hero-800.webp   800w,
    hero-1200.webp 1200w
  "
  sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
  src="hero-1200.webp"
  alt="Hero image"
  loading="lazy"
/>

Generate the sizes with sharp:

const sharp = require('sharp');

const widths = [400, 800, 1200];
for (const w of widths) {
    sharp('hero.jpg')
        .resize(w)
        .webp({ quality: 80 })
        .toFile(`hero-${w}.webp`);
}

How much compression to expect

Typical results when converting 24-bit PNG/JPEG photos to WebP at quality 80:

Original Format Compressed
3.2 MB (phone JPEG) WebP q80 280–400 KB
1.1 MB (PNG screenshot) WebP q80 80–150 KB
800 KB (product photo JPEG) WebP q80 60–120 KB

The gains are smaller for already-compressed JPEGs. The gains are largest for PNG photos (which were losslessly encoded) converted to lossy WebP.

Key takeaways

  • Convert photos and screenshots to WebP — it beats JPEG by 25–35% at the same quality.
  • Quality 75–85 is the standard web target for photos.
  • For logos and images with transparency, use PNG or WebP-with-alpha.
  • Use sharp (Node.js) or Pillow (Python) for batch processing.
  • Serve multiple sizes via srcset — don't serve a 3000 px image to a 400 px mobile screen.