← Back to blog Khalil Drissi

How to optimize images for the web

Listen to article
0:00

If a page feels slow, images are the first thing I check, and they are almost always the culprit. Text and code are tiny. A single uncompressed hero photo can outweigh your entire JavaScript bundle. The good news is that image optimization is mostly mechanical, and you can automate the whole thing.

Choose the right format

Format choice is the highest-leverage decision you make. JPEG is fine for photographs but it is old and inefficient. PNG is for images that need transparency or sharp edges, like logos and screenshots, and it is terrible for photos. The modern answer for almost everything is WebP, which gives you JPEG-quality photos at a fraction of the size, and AVIF when you want to push compression even further. I serve AVIF with a WebP fallback and a JPEG fallback below that.

Resize before you compress

This is the mistake I see constantly. People take a 4000 pixel wide camera photo, compress it, and display it in a 600 pixel column. The browser downloads all those wasted pixels and throws them away. Resize the image to the largest size it will actually be shown at first, then compress. Resizing alone often cuts file size by 80 percent before you have touched quality settings.

To know the right target width, look at how wide the image actually renders in your layout, then double it for high-density screens. A photo shown at 600 pixels should be exported at 1200 so it stays crisp on a retina display, and no larger. Going beyond that is pure waste, because the extra pixels never reach a single eye. I keep a small table of the render widths in my design and resize to match each one.

Automate it with sharp

I do not hand-edit images in an app. I run them through a script using the sharp library, which is fast and produces excellent output. A short pipeline resizes and converts everything in a folder:

const sharp = require('sharp');

sharp('input.jpg')
  .resize({ width: 1200, withoutEnlargement: true })
  .webp({ quality: 75 })
  .toFile('output.webp')
  .then(() => console.log('done'));

Quality 75 on WebP is my default. The difference between 75 and 90 is invisible on a screen but doubles the file size. Drop it into your build and every image gets the same treatment automatically, which fits neatly into a pipeline run through CI/CD with GitHub Actions.

Serve responsive sizes

A phone and a desktop should not download the same image. Generate a few widths and let the browser pick using srcset. The markup tells the browser what sizes exist and how wide the image renders, and it grabs the smallest one that still looks sharp:

<img
  src="photo-800.webp"
  srcset="photo-400.webp 400w, photo-800.webp 800w, photo-1200.webp 1200w"
  sizes="(max-width: 600px) 100vw, 600px"
  alt="A descriptive caption"
>

Lazy load everything below the fold

Add loading="lazy" to images that are not visible when the page first paints. The browser then defers loading them until the user scrolls near. This is a one-attribute change with a big payoff, because it stops offscreen images from competing for bandwidth with the content people actually see. Leave it off your hero image though, since you want that one to load immediately.

Always set width and height

Set explicit width and height attributes, or a CSS aspect ratio, on every image. Without them the browser does not know how much space to reserve, so the page jumps around as images load. That jump is layout shift, and it is one of the metrics Google grades you on. It also just feels broken to users. Reserving the space costs nothing and fixes it completely.

The payoff

Put these together and a typical image-heavy page goes from several megabytes to a few hundred kilobytes with no visible quality loss. That shows up directly in your load times and your Core Web Vitals. Lighter images mean a snappier site everywhere, including the moment you push it live through Cloudflare Pages and your visitors load it from the edge. Optimize once in the build, and you never have to think about it per-image again.

Comments
Leave a comment