Image Processing

How to Optimize Images for the Web (Without Guesswork)

A practical guide to resizing images, choosing WebP or JPEG, and checking visual quality. Includes a working Node.js example.

Large sage-green image panel with teal crop handles, containing a cream arch and a dark green vase, beside a smaller version of the same composition.

A product photo looks fine on your laptop, so you ship it. Then you open the same page on a phone and realize the browser is downloading a 3,000-pixel image to display it at a fraction of that size.

That is a common image-performance problem, and it is usually easier to fix than it sounds. You do not need a magic compression setting. You need to make a few decisions in the right order: dimensions first, format second, quality third.

This guide walks through that process and finishes with a small Node.js script you can run against one of your own images. No made-up savings percentages: the results depend on the photo you start with.

Start with where the image will appear

Before touching the encoder, look at the layout. A photograph used in a 320-pixel-wide product card does not need to be delivered at the same dimensions as the original camera file. A full-width hero needs a different treatment.

Check the rendered image width on desktop and mobile, then decide which output sizes you actually need. For example, a product card might use a smaller source on narrow screens and a larger one on high-density displays. That is a better starting point than generating one enormous file and letting CSS shrink it.

Keep the aspect ratio unless the design calls for a deliberate crop. Stretching a 3:2 photograph into a square changes the subject. If you need a square product tile, crop intentionally; if you need to show the whole product, fit it inside the square with appropriate padding.

Choose a format based on the image, not a trend

There is no single best image format for every file. For the kinds of assets most websites serve, these are useful starting points:

Format

Good starting use

What to watch

JPEG

Photographs and dependable fallback files

No transparency; high compression may introduce visible artifacts

WebP

Photographs, product images and many transparent graphics

Compare quality at the actual display size

AVIF

Images where a smaller transfer is worth testing

Encoding cost and appearance can vary by image

PNG

Assets requiring precise pixels or lossless output

Photographic PNGs can be unnecessarily large

SVG

Icons and simple vector illustrations

Not a replacement for a detailed photograph

For an ordinary product photograph, I would test WebP first and keep a JPEG version for fallback. AVIF may perform better for the same perceived quality, but verify that on your actual assets rather than assuming it always wins. For screenshots with small text and hard edges, inspect the result carefully; blurry labels are more noticeable than subtle changes in a photograph.

For browser support and format details, see the MDN image format guide.

Use quality as a test setting, not a promise

A quality value such as 80 is a reasonable experiment, not a universal standard. Each encoder interprets quality differently. Even two photos exported at the same setting can produce very different file sizes.

Instead of trying to hit a particular number, export a few versions and inspect the places where compression errors show up: hair, fabric, small lettering, gradients in the sky, or edges against a flat background. Check the image at its intended display size. Then compare the file sizes.

If the result looks rough, increase the quality or change the format. If you cannot see a meaningful improvement at a higher setting, keep the smaller file. The aim is a sensible trade-off, not the smallest possible number at any cost.

A small Node.js example with Sharp

The Sharp package handles resizing and encoding in Node.js. Here is a reproducible example that creates two widths, in WebP and JPEG, from one source photograph.

Start with a JPEG named product-photo.jpg that is at least 1,200 pixels wide. For the HTML example below, use a 3:2 photo. In an empty project, run:

bash
npm install sharp

Create optimize.mjs:

javascript
import sharp from "sharp";
import { stat } from "node:fs/promises";

const input = "product-photo.jpg";

for (const width of [640, 1200]) {
  await sharp(input)
    .rotate()
    .resize({ width, withoutEnlargement: true })
    .webp({ quality: 78 })
    .toFile(`product-${width}.webp`);

  await sharp(input)
    .rotate()
    .resize({ width, withoutEnlargement: true })
    .jpeg({ quality: 82 })
    .toFile(`product-${width}.jpg`);
}

const original = await stat(input);
const optimized = await stat("product-1200.webp");

console.log(`Original: ${Math.round(original.size / 1024)} KB`);
console.log(`1200px WebP: ${Math.round(optimized.size / 1024)} KB`);

Run it with:

bash
node optimize.mjs

You will get four files. Open the 640-pixel and 1,200-pixel versions side by side with the original. Look at edges, texture and any small details before deciding which settings to keep. The script prints actual file sizes from your image, not a theoretical compression claim.

The withoutEnlargement option prevents smaller input images from being scaled up. If you use a different source size or aspect ratio, update the width descriptors and dimensions in your markup to match the generated files.

Let the browser choose the right size

Generating smaller assets only helps if the browser can request them. The srcset and sizes attributes describe the available widths and the space the image will occupy. A picture element can offer WebP while keeping a JPEG fallback.

Assuming the files from the script are served from your site's /images/ directory, this is a simple example for a 3:2 product image:

html
<picture>
  <source
    type="image/webp"
    srcset="/images/product-640.webp 640w,
            /images/product-1200.webp 1200w"
    sizes="(max-width: 720px) 100vw, 720px">
  <img
    src="/images/product-1200.jpg"
    srcset="/images/product-640.jpg 640w,
            /images/product-1200.jpg 1200w"
    sizes="(max-width: 720px) 100vw, 720px"
    width="1200"
    height="800"
    alt="Ceramic bowl on a wooden table"
    loading="lazy">
</picture>

The width and height attributes reserve space before the image loads. Use loading="lazy" for images below the fold, not automatically for the main image visible at the top of the page. If this were your primary hero image, I would normally let it load promptly instead.

For a closer look at responsive-image syntax, Google's Learn Images guide covers srcset, sizes and picture.

Measure the page, not just the exported file

After generating and serving your variants, open DevTools on a mobile-sized viewport. Check which image the browser requested and how many bytes were transferred. Confirm that a narrow device did not download the largest version unnecessarily.

Then check the page as a whole. A smaller image may help loading, but it will not fix a slow server, a render-blocking script or an image pushed far down the page by layout changes. If the image is the page's Largest Contentful Paint candidate, its delivery matters more than the size of a thumbnail far below the fold.

A quick review usually answers four useful questions:

  • Did we generate dimensions that match actual layout needs?

  • Did the browser choose the expected source?

  • Does the optimized version still look good at display size?

  • Did the page get measurably better, not just the image file?

When image optimization becomes an application workflow

A one-off script works well for a small site. Applications with user uploads, product catalogs or several image sizes have a different problem: they need the same operations applied consistently every time.

That is where a dedicated media API can help. Imazeo's Image API supports authenticated resizing, cropping and format conversion, including WebP and AVIF. You can define output dimensions and quality as part of a transformation request. Keep your API credentials on the server; do not put project keys into public browser code.

Whether you use a build script or an API, the useful habit is the same: pick the right dimensions, test the format, inspect the result, and measure the delivery.

Try next

Start with one image from a real page rather than converting your whole asset library at once. Create two widths, compare WebP with JPEG, and check which file your phone actually downloads. If you want to turn that experiment into an authenticated processing workflow, see the Imazeo image transformation quickstart.