How to Compress Images for the Web Without Losing Quality
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) orPillow(Python) for batch processing. - Serve multiple sizes via
srcset— don't serve a 3000 px image to a 400 px mobile screen.