Skip to content
NEWAI search optimizationSee how

Performance

Images are usually the whole problem

Most slow pages are slow because of images. The formats worth using, how to size them correctly, when lazy loading backfires, and the five-minute audit.

Bizup LLC

Seattle web design, development and SEO studio

6 min read

Speed optimization

Start here before you touch anything else. Open DevTools, filter the network panel to images, and sort by size. Then compare each image's intrinsic dimensions to the box it is displayed in.

On most sites we audit, that five-minute exercise accounts for more wasted weight than every other issue combined — and unlike JavaScript, fixing it requires no architectural decisions and breaks nothing.

The four failures, in order of frequency

  1. Serving a 3000-pixel-wide file into a 600-pixel box. The browser downloads and decodes all of it, then throws most of it away. This is the single most common waste on the web and it usually comes from a CMS upload with no resizing step.
  2. Lazy-loading the LCP image. A hero marked loading="lazy" cannot be fetched until layout has established it is in the viewport, which adds a round trip to the one image your LCP depends on.
  3. No dimensions, so the layout jumps. Without width and height attributes or an aspect-ratio, the browser cannot reserve the box, and everything below shifts when the file lands. Google's CLS threshold is 0.1 at the 75th percentile; a few unsized images will exceed it on their own.
  4. Shipping JPEG and PNG in 2026. A format change is usually the largest byte reduction available for zero visual compromise.

Formats, briefly

There are only three answers worth considering, and the choice is simpler than the discourse suggests.

  • AVIF for photographs. Best compression of the three at equivalent quality, supported in current Chrome, Firefox, Safari and Edge. Encoding is slower, which only matters in your build pipeline.
  • WebP as the fallback and the safe default. Google's own comparison puts WebP between 25% and 34% smaller than a comparable JPEG at equivalent quality.
  • SVG for anything drawn rather than photographed — logos, icons, diagrams. Resolution-independent and usually tiny. Run it through a minifier; design tools emit a great deal of metadata nobody needs.

Skip the rest. A <picture> element with an AVIF source, a WebP source and a raster fallback removes the browser-support question entirely and costs you three lines.

html

<picture>
  <source type="image/avif" srcset="/case-640.avif 640w, /case-1280.avif 1280w">
  <source type="image/webp" srcset="/case-640.webp 640w, /case-1280.webp 1280w">
  <img
    src="/case-1280.jpg"
    width="1280" height="853"
    sizes="(max-width: 768px) 100vw, 680px"
    alt="The finished reception area, lit from the left"
    loading="lazy" decoding="async"
  >
</picture>
In Next.js, next/image handles format negotiation and srcset generation for you — but it cannot guess sizes, and that is the attribute that matters most.

Sizing: the part everyone gets wrong

Two attributes do the work, and they are frequently confused. srcset tells the browser which files exist and how wide each one is. sizes tells the browser how wide the image will be displayed, as a CSS length, at a given viewport width.

The browser needs sizes before CSS has been applied, because it starts fetching images during HTML parsing. That is why it cannot work this out from your stylesheet, and why an omitted or default sizes value is so costly: the browser assumes the image fills the viewport and picks the largest candidate.

So a sizes attribute that says 100vw on an image that is actually 400 pixels wide inside a grid will download a file several times larger than required, on every single visit. Get this one attribute right and you will often halve your image weight without changing a single file.

Lazy loading: two rules

It is a useful default and a bad universal policy.

  • Never lazy-load anything above the fold, and especially not the LCP element. Mark the hero with fetchpriority="high" instead, so the browser promotes it ahead of the other images it discovers.
  • Lazy-load everything below it. Native loading="lazy" needs no JavaScript and no library. If you are still shipping a lazy-load script, delete it — it is almost certainly slower than the browser's own implementation and it fails when scripts fail.

The rule of thumb that survives most layouts: the first one or two images in the document are eager, everything after is lazy. If you cannot tell which element is your LCP, PageSpeed Insights names it for you.

The background-image trap

An image set in CSS as background-image is discovered late, because the browser has to download and parse the stylesheet, build the render tree, and determine that the element applies before it knows the file exists. The preload scanner cannot see it at all.

If a CSS background is your LCP element, you have added an avoidable round trip to your most important metric. Either move it to a real <img> — which also gives you alt text, dimensions and responsive candidates — or preload it explicitly. The first option is almost always correct; decorative gradients and textures are the legitimate exception.

Build a pipeline, not a habit

Manual optimisation does not survive contact with a marketing team. Whatever you do needs to happen automatically on upload or at build time, or it stops happening in month three.

What that means concretely: resizing to a fixed set of widths, conversion to AVIF and WebP, stripping metadata, and refusing to serve the original. The last part matters — if the original is reachable, something will eventually reference it. Any decent image CDN does all of this, as does next/image with a configured device-width list.

What to do this week

  1. Find your LCP element on your three highest-traffic templates. Make sure it is eager, dimensioned and in a modern format.
  2. Audit every sizes attribute against the real rendered width. Fix the ones claiming 100vw.
  3. Add width and height to every image that lacks them. This is pure CLS reduction with no trade-off.
  4. Set up the pipeline so none of the above can regress.

That is four tasks, none of them architectural, and on a typical site it is the largest performance gain available for the effort. If you would rather it were done and verified against your real-user data, that is our speed optimization service — and what actually moves LCP, INP and CLS covers the metrics these changes move.

Keep reading

More onPerformance

All insights
Performance

7 min read

Core Web Vitals: what actually moves LCP, INP and CLS

A practitioner's guide to the three Core Web Vitals: the real thresholds, why Lighthouse disagrees with Search Console, and the fixes that move field data.

Read the article
Performance

7 min read

The real cost of a page builder, measured

What a page-builder site actually ships: a megabyte of CSS across dozens of files, duplicated JavaScript runtimes, and a DOM deep enough to slow interaction.

Read the article