← All articles
🖼️ PerformanceIntermediate · 11 min read

Image Optimisation: Format, Sizing, and the Lazy-Load Rules

A working playbook for the part of frontend performance with the biggest wins and the most common mistakes.

By Kajal Pansuriya · Reviewed by Kishan Vaghani · Published May 26, 2026

On most sites, the LCP element is an image. It is also the part of the frontend that is most commonly mishandled — wrong format, wrong size, lazy when it should be eager, eager when it should be lazy, missing width and height attributes that cause layout shift the moment the bytes arrive. None of this is hard. It is just a small set of rules that need to be applied consistently.

The format ladder

Image formats are not interchangeable. For a typical photograph rendered at 1600px wide, the file sizes look roughly like this:

Serve the best format the browser accepts with the <picture> element:

<picture>
  <source type="image/avif" srcset="/hero-1600.avif" />
  <source type="image/webp" srcset="/hero-1600.webp" />
  <img
    src="/hero-1600.jpg"
    alt="Team working in a shared editor"
    width="1600"
    height="900"
  />
</picture>

The browser walks the sources top to bottom and uses the first one it can decode. The <img> is the fallback for browsers that ignore <picture> entirely, which is now a vanishingly small share.

The sizing strategy

Serving a 4000px image to a 800px viewport wastes bytes and CPU. The fix is srcset plus sizes — you supply the browser with several widths and a hint about which one it should pick:

<img
  src="/hero-1200.jpg"
  srcset="
    /hero-480.jpg 480w,
    /hero-768.jpg 768w,
    /hero-1200.jpg 1200w,
    /hero-1920.jpg 1920w
  "
  sizes="
    (max-width: 768px) 100vw,
    (max-width: 1200px) 90vw,
    1200px
  "
  alt="..."
  width="1200"
  height="675"
/>

Three to four widths is the right number for most images. Match them to your common breakpoints — a mobile width (480 or 640), a tablet width (768 or 1024), and one or two desktop widths. The sizes attribute tells the browser the rendered width at each breakpoint so it can pick the smallest source that still looks crisp at the device pixel ratio.

Always set width and height attributes that match the image's natural aspect ratio. The numbers themselves do not have to match the rendered size — the browser uses the ratio to reserve space before the image loads. Without them, CLS jumps every time an image arrives after layout.

Lazy loading rules

Native loading="lazy" is supported in every modern browser. It defers the network request for an image until the browser thinks the user is likely to scroll to it. The rules for using it are short:

The LCP image is special

The single image that ends up as your Largest Contentful Paint element deserves its own treatment. Three moves push LCP down by hundreds of milliseconds each:

<head>
  <link
    rel="preload"
    as="image"
    href="/hero-1200.webp"
    imagesrcset="/hero-768.webp 768w, /hero-1200.webp 1200w"
    imagesizes="(max-width: 768px) 100vw, 1200px"
    type="image/webp"
  />
</head>
<body>
  <img
    src="/hero-1200.webp"
    srcset="..."
    sizes="..."
    alt="..."
    width="1200"
    height="675"
    fetchpriority="high"
  />
</body>

Hosting and CDN

An image CDN handles format negotiation, on-the-fly resizing, and caching automatically. Cloudinary, ImageKit, Imgix, and the built-in pipelines from Vercel and Netlify all do the same job: you upload one source image, the CDN serves the right format and size based on the request headers. Next.js's <Image> component wires up to whichever pipeline your host provides.

The trade-off is vendor lock-in and bandwidth cost. Vercel meters image optimisation by source image and transformed image volume, and a popular site can run a four-figure monthly bill before it notices. For self-hosted sites, building the responsive variants at deploy time with sharp and serving them from a plain CDN is usually cheaper and just as fast.

What real numbers look like

On a marketing page we audited last month, the hero was a single 1.2MB JPEG served at 1920px regardless of viewport. Mobile LCP on a 4G profile was 4.2s. The changes were unremarkable:

  1. Wrapped the hero in <picture> with WebP and AVIF sources. JPEG fallback retained.
  2. Generated four widths (480, 768, 1200, 1920) with sharp at build time. Hero now serves a 78KB AVIF at 1200px on desktop and a 22KB AVIF at 480px on mobile.
  3. Added <link rel="preload"> for the hero and fetchpriority="high" on the img.
  4. Added loading="lazy" to every other image on the page (the testimonial photos, the feature illustrations, the footer logos).

Mobile LCP dropped from 4.2s to 1.9s on the same 4G profile. Total image bytes on first paint went from 1.2MB to 22KB. The CSS, JavaScript, and HTML didn't change at all.

The habit that compounds

Image work is the closest thing to a free performance win that exists in frontend engineering. The rules are short, the tools are mature, and the wins are measured in seconds rather than tens of milliseconds. The habit that compounds is auditing the LCP image on every new template before it ships — five minutes of checking format, size, preload, and lazy state — because the alternative is finding the regression in production three weeks later when a field-data dashboard flags it.

Related reading

For the metric definitions, start with Core Web Vitals explained. The companion post on font loading without layout shift covers the other big asset class. To find out whether your changes are actually moving the field number, set up real-user monitoring. The full performance silo is on the web performance topic page.

About the writers

Author

Kajal Pansuriya

Developer Educator, ShareCode

Developer educator at ShareCode. Writes the tutorial track — Python, JavaScript debugging, coding-interview prep, and the everyday code-quality habits that hold up in real codebases.

Python fundamentals & teaching beginnersJavaScript debugging & DevToolsCoding-interview preparationClean code & code review
Reviewed by

Kishan Vaghani

Founder & Lead Engineer, ShareCode

Founder of ShareCode. Writes the engineering deep-dives on this site — WebRTC, Firebase Auth, real-time sync, and the production patterns behind the editor itself.

Real-time collaboration & CRDTsWebRTC & low-latency mediaFirebase authentication & security rulesNext.js & full-stack JavaScript

Auditing your hero image?

Paste the markup and the asset sizes into a code space and walk through the format ladder, srcset, and preload checklist with a teammate. Most hero-image audits surface a ten-times bandwidth saving in the first pass.

Open a code space