When a page is slow, the images are nearly always the reason, and nearly always for the same one: they are far bigger than the space they are shown in. A camera photo is 5000 pixels wide; the column it sits in is 800. So there are two jobs, in this order. Resize each image to twice the size it is shown at, at most, then encode it well.
Drop the images in and each one shows its new size and the saving.
The first job does most of the work. A 5184 × 3456 camera photo of 11.1 MB came out at 2.58 MB when we re-encoded it at full size, and at 273 KB when we also resized it to 1600 pixels wide. Same settings, a tenth of the bytes.
Resize to 1600 pixels wide and save a JPEG at quality 82, without the camera's metadata:
magick photo.jpg -resize 1600x -quality 82 +profile '!icc,*' photo-1600.jpg
1600x sets the width and lets the height follow. +profile '!icc,*' removes
everything except the colour profile. Install it with brew install imagemagick
on a Mac or apt install imagemagick on Debian and Ubuntu.
Work out the size first
Open the page, right-click the image and choose Inspect. In Chrome's
Elements panel, hovering over the image's src address shows its rendered size
(how big it is on the page, in CSS pixels) and its intrinsic size (how big the
file is).
Make the file twice the rendered width, and no more. A high-density screen packs two or three device pixels into each CSS pixel, so an image shown 800 pixels wide needs 1600 to look sharp. Beyond twice, the extra pixels cost bytes nobody can see. How to resize images has the sizes that cover most layouts, and the aspect-ratio arithmetic.
-strip is the usual advice, and it removes the colour profile along with the
camera data. On an sRGB image that changes nothing. The test photo here is in
Adobe RGB, as camera exports often are (phone photos are often Display P3), and
stripped, its colours go flat. So does cwebp by default: add -metadata icc.
Then pick the format
We took a public-domain photo of the Tetons from a Canon EOS 7D, resized it to 1600 × 1067, encoded it with each encoder and scored the results with SSIMULACRA 2, a metric built to track what people see. Its authors put 70 at "high quality", where artefacts are barely noticeable side by side, and 80 at "very high quality", where they can't be seen side by side at all. Higher is better.
| Photo, 1600 × 1067 | Size | Score |
|---|---|---|
| Original, 5184 × 3456 | 11.1 MB | |
| JPEG, quality 82 | 273 KB | 81.3 |
| JPEG, quality 70 | 189 KB | 74.4 |
| WebP, quality 90 | 241 KB | 81.2 |
| WebP, quality 80 | 138 KB | 71.4 |
| AVIF, quality 75 | 185 KB | 80.3 |
| AVIF, quality 60 | 119 KB | 73.1 |
Read the table in pairs of similar scores. At about 81, AVIF is a third smaller than JPEG and WebP only 12% smaller. At about 73, AVIF is 37% smaller than JPEG. WebP's quality number is not JPEG's: WebP 80 is half the size of JPEG 82, but it scores ten points lower.
A flat illustration behaves differently. The 3000 × 2000 mountain sunset we use for our screenshots, resized to the same 1600 pixels:
| Illustration, 1600 × 1067 | Size | Score |
|---|---|---|
| JPEG, quality 82 | 55.9 KB | 84.3 |
| WebP, quality 90 | 25.8 KB | 83.1 |
| AVIF, quality 60 | 6.8 KB | 84.9 |
Same score, an eighth of the size. Smooth gradients and hard edges are what AVIF is best at, and what JPEG is worst at.
Resize once to a lossless PNG, then encode it each way:
magick photo.jpg -resize 1600x +profile '!icc,*' photo-1600.png
cwebp -q 90 -metadata icc photo-1600.png -o photo-1600.webp
avifenc -q 60 photo-1600.png photo-1600.avif
avifenc comes from libavif: brew install webp libavif, or
apt install webp libavif-bin. Start AVIF at quality 60 and WebP at 90, and lower
them while you can't see a difference on the most detailed image you have.
Every current major browser shows WebP and AVIF. The HTML below keeps a JPEG for anything that doesn't.
Already on a Mac: sips
sips is built into macOS and resizes with -Z, which fits the long edge. On
macOS 26.5 it also writes AVIF:
sips -Z 1600 -s format avif photo.jpg --out photo-1600.avif
That gave 203 KB and a score of 83.6 on the test photo. Its quality scale is its
own: -s formatOptions 82 on a JPEG gave 350 KB, scoring 88.4, much larger than
ImageMagick's quality 82. sips also keeps the camera metadata (make, model and
the rest), so it isn't the tool for removing a photo's location.
Serve the right file to each screen
One file for every screen means phones download the desktop image. srcset
lists the widths you made, sizes tells the browser how wide the image will be
shown, and the browser picks the smallest file that is sharp enough:
<picture>
<source type="image/avif"
srcset="photo-800.avif 800w, photo-1600.avif 1600w"
sizes="(max-width: 840px) 100vw, 800px">
<img src="photo-1600.jpg"
srcset="photo-800.jpg 800w, photo-1600.jpg 1600w"
sizes="(max-width: 840px) 100vw, 800px"
width="1600" height="1067"
alt="The Teton Range under snow, lit by the low sun"
loading="lazy" decoding="async">
</picture>
Here the image fills the screen below 840 pixels and sits in an 800-pixel column
above it. width and height are the file's own size: the browser uses them to
reserve the right space before the image loads, so the text below it doesn't
jump. Your CSS can still scale it (img { max-width: 100%; height: auto; }).
loading="lazy" belongs on images below the first screen. The big image at the
top is usually what the browser counts as the page having loaded (Largest
Contentful Paint), and lazy-loading it delays exactly that. Leave it off there,
or use fetchpriority="high" instead. web.dev has
the details.
Make the set in the browser
The Image Resizer exports several widths in several formats at once, which
is the set srcset wants.
Two widths times two formats: four files, in one ZIP.
- Open Image Resizer and drop the image in.
- In Export, open Custom and type
800wunder Shorthand, then Add. Do the same for1600w. - Under Formats, turn off PNG and turn on JPEG and WebP. Each gets its own quality slider: JPEG starts at 85, WebP at 90.
- Press Export 4 files. You get a ZIP with names like
photo-800w.webpandphoto-1600w.jpg, ready forsrcset.
For a folder of images all going to one size, Compress Image is quicker: drop them in, set Max dimension to the width you need (1600 px for an 800-pixel column on a sharp screen), and switch on Convert to WebP. Remove metadata is on by default. The presets map to quality 85 (High quality), 75 (Balanced) and 55 (Smallest). It uses your browser's own encoder, so its sizes won't match the tables above exactly. If a file would come out larger, you keep the original, marked Already optimized: the illustration above, re-encoded at full size at quality 82, grew from 115 KB to 118 KB, which is what that rule is for.
The browser tools here make JPEG and WebP reliably. The Image Converter lists
AVIF, but it uses the browser's encoder, and where the browser can't encode AVIF
the file can come out as a PNG with an .avif name. For AVIF, use avifenc or
sips above. Compress Image only writes AVIF for an AVIF you gave it, and falls
back to WebP when the browser can't.
Picking the format itself is covered in How to convert between image formats. When you're done, reload the page with the Network panel open and filter by Img: the sizes in that column are what your visitors download.