Image Optimization for the Web

Image Optimization for the Web: Formats, Lazy Loading, and CDNs

Images are often one of the biggest opportunities to reduce page weight and improve Largest Contentful Paint. A single unoptimised hero image can outweigh the JavaScript needed for a simple page, consume unnecessary data, and delay the largest visible element on slower connections.

Here's what Dev3 Studio does on every project.

Live Google PageSpeed Insights tool with an empty URL field
Run the finished page through Google's live PageSpeed Insights tool to measure image-related performance opportunities.

Modern Image Formats

The format landscape has shifted. Here's what matters:

JPEG — the old reliable. Good compression for photographs, still the fallback for older browsers. PNG — lossless, supports transparency. Use for logos and sharp-edged graphics, avoid for photographs. SVG — vector format for icons, logos, and illustrations. Infinitely scalable, styleable with CSS.

WebP — Google's compression study found lossy WebP images were 25–34% smaller than comparable JPEG images at equivalent SSIM quality. It supports transparency and is a strong default for modern browsers.

AVIF — can compress even further for many photographs, but results vary by image and encoding is slower. Serve AVIF first with WebP and JPEG fallbacks when your delivery pipeline supports it.

web.dev guide to choosing an image format
web.dev's current format guide explains when JPEG, PNG, WebP, AVIF, and SVG are appropriate; measure your own asset because results vary.

Dev3 Studio's default: AVIF with WebP fallback and JPEG as the last resort. The <picture> element makes this straightforward:

<picture>
  <source srcset="/images/hero.avif" type="image/avif" />
  <source srcset="/images/hero.webp" type="image/webp" />
  <img src="/images/hero.jpg" alt="Project showcase hero image" width="1200" height="630" />
</picture>

The browser evaluates the <source> elements in order and uses the first compatible candidate, falling back to the <img> element.

MDN reference for the HTML picture element
MDN's live picture reference documents source selection for formats, media conditions, and art direction.

Responsive Images

Stop serving a 2400px image to a phone screen. Use srcset and sizes to let the browser pick an appropriate source:

<img
  srcset="
    /images/hero-640w.webp   640w,
    /images/hero-1024w.webp 1024w,
    /images/hero-1536w.webp 1536w,
    /images/hero-2048w.webp 2048w
  "
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 50vw"
  src="/images/hero-1024w.webp"
  alt="Project showcase"
  width="2048"
  height="1080"
/>

Generate multiple sizes at build time (640w, 1024w, 1536w, 2048w is a solid set). The sizes attribute tells the browser how wide the image will actually be displayed — without it, srcset is guessing.

MDN guide to responsive images
MDN's responsive-image guide explains how srcset and sizes let the browser select an appropriate resource.

Frameworks like Next.js can handle much of this automatically. The next/image component generates responsive srcset candidates, preserves aspect ratio, lazy-loads by default, and can serve configured modern formats.

Current Next.js Image component API documentation
The live Next.js Image API documents its sizing, loading, optimization, and generated responsive-image behaviour.

Lazy Loading

Native image lazy loading lets the browser defer off-screen images without application JavaScript:

<!-- Below-the-fold images: lazy load -->
<img src="/images/feature.webp" alt="Feature screenshot" loading="lazy" width="800" height="450" />
 
<!-- LCP / above-the-fold image: load eagerly with high priority -->
<img src="/images/hero.webp" alt="Hero image" loading="eager" fetchpriority="high" width="1200" height="630" />

Don't lazy load the image that is likely to become LCP. Your hero image and anything visible on initial load should use loading="eager" or omit the attribute. For the single most important image, fetchpriority="high" can help the browser discover and prioritise it. Use lazy loading for images that begin below the fold.

web.dev guide to browser-level image lazy loading
web.dev's live lazy-loading guide documents native loading="lazy" and the cases where above-the-fold images should load eagerly.
web.dev documentation for Largest Contentful Paint
web.dev's current LCP reference explains the metric, its thresholds, and how to measure the responsible element.

Compression

For JPEG and WebP, a quality setting around 75–85 is a useful starting point, not a universal rule. Compare representative images visually and choose the lowest setting that preserves the detail your design needs.

Live Squoosh image compression app before an image is loaded
Squoosh's live comparison tool lets you inspect quality and file size side by side using your own image; no client asset is uploaded in this capture.

Tools that work well: Sharp for Node.js build pipelines, Squoosh for one-offs, and ImageMagick for CLI workflows. Use lossy compression for photographs and test lossless or near-lossless settings for screenshots and text-heavy images. Automate compression in your build pipeline — manual compression doesn't scale.

CDNs and Image Transformation

Don't serve images from your origin server if you can avoid it.

Cloudflare Images documentation
Cloudflare's live Images documentation covers transforming, optimizing, and delivering images through its network.

Cloudflare — its CDN caches eligible assets at the edge, while Polish adds plan-dependent image compression and format conversion. Vercelnext/image uses its Image Optimization API to resize and cache requested variants. Cloudinary and imgix — dedicated image CDNs with URL-based transforms. Powerful, but they add another service dependency.

Placeholder Strategies

What you show while images load matters for perceived performance and layout stability.

Blur-up — show a tiny, blurred version that transitions to the full image. Next.js supports this with placeholder="blur" and blurDataURL. Dominant color — fill the space with the image's dominant colour. Skeleton — use a neutral placeholder matching the image dimensions.

Current Next.js guide to using images
The live Next.js image guide documents local and remote images, sizing, and placeholder behaviour.

The non-negotiable: set width and height (or reserve the aspect ratio in CSS) so the browser can allocate space before the image loads. Unsized images can cause layout shifts that worsen CLS.

The Practical Checklist

What Dev3 Studio does on every project:

  • Serve AVIF/WebP with JPEG fallback
  • Generate responsive sizes with srcset
  • Lazy load below-the-fold images
  • Set fetchpriority="high" on the LCP image
  • Compress to quality 80 (WebP) / 75 (AVIF)
  • Serve through a CDN (Vercel or Cloudflare)
  • Set explicit dimensions to prevent layout shift
  • Use blur-up placeholders for hero images
  • Run Lighthouse after every deployment

Image optimization isn't glamorous, but it's one of the highest-impact things you can do for web performance. A well-optimized site loads faster, ranks higher, costs less bandwidth, and respects your users' time and data. Do it once, automate it, and move on.