Image Optimization for the Modern Web: WebP, AVIF, and Lazy Loading - SwiftyKits
Interactive Front-End Performance Guide

Image Optimization for the Modern Web: WebP, AVIF, and Lazy Loading

12 Min Read Saves Your Progress

Images are often the largest payload on a web page. A site can have clean HTML, optimized CSS, and fast JavaScript, yet still load slowly because a 2,500px image is being downloaded to fill a 400px mobile container.

The solution is not simply "compress every image."

Modern image optimization combines the right format, the right dimensions, the right compression level, and the right loading strategy. WebP and AVIF can reduce file size compared with JPEG and PNG, while srcset and sizes prevent browsers from downloading unnecessarily large images. Browser-level lazy loading can then defer images that are not needed for the initial viewport.

The objective is to make every image appropriate for the device and display size without destroying visual quality.

For many image-heavy pages, this can produce dramatic payload reductions. A 1 MB JPEG does not necessarily need to become a 300 KB JPEG. It may become a 150 KB AVIF or WebP and be served at the correct dimensions for the user's screen.

This guide shows exactly how to implement that workflow using native HTML and CSS.

Modern Image Delivery Pipeline 2.4 MB Raw File → 48 KB Mobile Variant
2.4 MB
1. Original Source
Uncompressed 4000×2500 camera export
Max 1600px
2. Resize Breakpoints
Generate 480w, 768w, 1200w & 1600w widths
AVIF / WebP
3. Encode Formats
Export next-gen formats + JPG fallback
<picture>
4. srcset + sizes
Declare candidate widths & layout slot size
48 KB – 140 KB
5. Browser Selection
Downloads only the exact required variant
Developer pipeline showing how a 2.4 MB source image is resized, encoded into AVIF/WebP, and negotiated via srcset so the browser downloads only the required variant.
Click subheadings to check off steps
Auto-saves to your browser
Track progress in the sidebar

JPEG and PNG vs. WebP and AVIF

The first optimization decision is the image format. JPEG remains useful for photographs and situations where maximum legacy compatibility is important. PNG is particularly useful when lossless quality or transparency is required. But WebP and AVIF generally provide better compression for many modern web use cases.

Format Compression Transparency Best Use Browser Compatibility
JPEG Lossy No Photographs Excellent
PNG Lossless / limited lossy workflows Yes UI graphics, transparency, screenshots Excellent
WebP Lossy + lossless Yes General web images Excellent in modern browsers
AVIF Lossy + lossless Yes Maximum compression efficiency Broad modern-browser support

The important point is that you do not need to choose one format for every visitor. HTML allows you to offer AVIF first, WebP second, and a JPEG fallback.

Use <picture> for Format Negotiation

A production-friendly implementation looks like this:

<picture> <source srcset="/images/hero.avif" type="image/avif" > <source srcset="/images/hero.webp" type="image/webp" > <img src="/images/hero.jpg" width="1600" height="900" alt="Business team working together" > </picture>

The browser evaluates the <source> elements and selects a compatible image. If AVIF is supported, it uses the AVIF version. If not, it tries WebP and eventually falls back to JPEG. The <img> remains essential because it provides the actual image element and fallback. This is much safer than attempting to detect browser support with JavaScript.

Add Responsive Variants with srcset

Format selection solves one problem, but it does not solve image dimensions. Suppose your desktop hero is 1600 × 900. Sending that same file to a phone displaying the image at approximately 390px wide is wasteful.

Create several versions (hero-480.avif, hero-768.avif, hero-1200.avif, hero-1600.avif) and use srcset:

<picture> <source type="image/avif" srcset=" /images/hero-480.avif 480w, /images/hero-768.avif 768w, /images/hero-1200.avif 1200w, /images/hero-1600.avif 1600w " > <source type="image/webp" srcset=" /images/hero-480.webp 480w, /images/hero-768.webp 768w, /images/hero-1200.webp 1200w, /images/hero-1600.webp 1600w " > <img src="/images/hero-1200.jpg" srcset=" /images/hero-480.jpg 480w, /images/hero-768.jpg 768w, /images/hero-1200.jpg 1200w, /images/hero-1600.jpg 1600w " sizes="100vw" width="1600" height="900" alt="Business team working together" > </picture>

The w descriptors describe the intrinsic width of each candidate. The browser combines these candidates with sizes and the device's pixel density to select an appropriate resource.

Configure the sizes Attribute Accurately

Consider a product card that occupies half the desktop content area (50vw) but the full width on mobile (100vw):

<img src="/images/product-1200.jpg" srcset=" /images/product-400.jpg 400w, /images/product-800.jpg 800w, /images/product-1200.jpg 1200w " sizes=" (max-width: 767px) 100vw, 50vw " width="1200" height="800" alt="Product" >

The browser can then choose an appropriate candidate instead of blindly downloading the largest file.

How srcset & sizes Work Together Responsive Candidate Selection
1. srcset Attribute
“Here are the image files I have available.”

Lists each file path alongside its true pixel width (400w, 800w, 1200w).

2. sizes Attribute
“Here is how wide the slot will render on screen.”

Tells the browser the layout width at each breakpoint before external CSS finishes loading.

3. Browser Engine
“I'll calculate DPR and fetch the best match.”

Multiplies rendered slot width by device pixel ratio (1x, 2x) and requests the optimal file.

Handle Retina Displays Without Sending Oversized Files to Everyone

A 400 CSS-pixel-wide image may need an image resource substantially wider than 400 physical pixels on a high-DPI screen. For fixed-width elements like logos, you can provide density-specific resources:

<img src="/images/logo.png" srcset=" /images/logo.png 1x, /images/logo@2x.png 2x " width="300" height="100" alt="Company logo" >

For fluid layouts, however, width descriptors such as 400w, 800w, and 1200w combined with sizes are usually more flexible.

The Golden Rule of Lazy Loading

Lazy loading is one of the easiest image optimizations to implement incorrectly. For images below the initial viewport:

<img src="/images/product-01.webp" width="800" height="600" loading="lazy" alt="Product photograph" >

The browser can defer loading the image until it is approaching the viewport. But there is one critical exception.

Never Lazy Load the LCP Hero Image

Do not put loading="lazy" on your hero image if that image is the page's Largest Contentful Paint element. Lazy loading can delay its discovery and download. Instead:

<img src="/images/hero.webp" width="1600" height="900" fetchpriority="high" alt="Hero image" >

Google's web.dev guidance explicitly warns against lazy-loading the LCP image and recommends fetchpriority="high" when an image is likely to be the LCP element. Use it selectively—putting high priority on too many resources weakens its effect.

Viewport Loading Priority Rules Above the Fold vs. Below the Fold
Above-the-Fold / LCP Hero Eager Priority
  • DO NOT add loading="lazy"
  • Add fetchpriority="high"
  • Include explicit width & height
Below-the-Fold Images Deferred Load
  • Add native loading="lazy"
  • Use srcset & sizes for responsive cards
  • Include explicit width & height to prevent CLS

Compressing Images Without Destroying Sharpness

Compression is not about making every image as small as technically possible. It is about finding the smallest file that still looks correct at its actual display size.

Photographs tolerate lossy compression relatively well. But screenshots and UI graphics are different—sharp text, thin lines, icons, and high-contrast edges expose compression artifacts much more easily.

Don't Compress a UI Screenshot Like a Photograph

Aggressive lossy compression on UI screenshots creates fuzzy text, ringing around letters, blocky edges, and blurry icons. Test WebP or AVIF carefully and compare the actual rendered result, or use lossless WebP/PNG where sharp text and transparency are critical.

UI Screenshot Compression Quality Protecting Sharp Text & Thin Lines
Over-Compressed Lossy UI Blurry Artifacts
Revenue$142,302
Conversion4.82%
Ringing around numbers, muddy contrast & fuzzy edges.
Properly Resized & Tuned UI Crisp Edges
Revenue$142,302
Conversion4.82%
Resized to exact display width (1200×750) with sharp typography.

Compress at the Intended Display Size (Don't Resize Only With CSS)

Instead of aggressively compressing a 4000 × 2500 file that displays at 800 × 500, resize it to approximately the largest size you actually need first. This reduces both file bytes and image decoding/rendering work.

Remember: .hero img { width: 100%; } controls display size, not downloaded file size. Combine CSS with srcset and sizes so the browser downloads the smaller resource.

A Practical 8-Step Image Compression Workflow

For a custom website, use this workflow for every major image:

1. Identify the purpose

Determine whether the asset is a photograph, logo, screenshot, UI graphic, icon, decorative background, or hero/LCP image.

2. Resize the source

Do not upload a 5000px image when the largest rendered size is 1200px.

3. Generate modern formats

Create image.avif, image.webp, and image.jpg where they provide a meaningful size/quality advantage.

4. Generate responsive widths

Export breakpoints such as 400px, 800px, 1200px, and 1600px based on the component's actual rendered sizes.

5. Add intrinsic dimensions

Include width="1200" height="800" on the <img> tag so the browser reserves layout space and prevents CLS.

6. Set loading behavior

Use loading="lazy" below the fold, and fetchpriority="high" (without lazy loading) on the LCP hero image.

7. Test visual quality

Open the actual image at its rendered size and inspect text, faces, edges, gradients, shadows, fine details, and transparency.

8. Test the page in DevTools and Field Data

Use the Chrome DevTools Network panel, Lighthouse/PageSpeed Insights, and Performance panel to verify which image candidate was actually downloaded and whether the LCP image starts early enough.

Common Image Optimization Mistakes

Audit your templates against the 6 classic mistakes

  • Mistake 1: Lazy-loading everything: Putting loading="lazy" on every image delays the LCP element.
  • Mistake 2: Using a 3000px image for a 300px card: CSS resizing doesn't reduce network payload. Use srcset and sizes.
  • Mistake 3: Converting everything blindly to AVIF: Measure actual output size and visual quality rather than assuming AVIF is always smaller.
  • Mistake 4: Removing width and height: Responsive images still need HTML dimensions to establish intrinsic aspect ratio.
  • Mistake 5: Compressing screenshots like photographs: Aggressive lossy settings ruin UI text and sharp lines.
  • Mistake 6: Using JavaScript for basic lazy loading: Native loading="lazy" handles normal offscreen images without extra JS overhead.

The Production Checklist

Verify all 15 production image standards before launch

Before launching a custom website, audit every significant image against this list:

Correct image dimensions
Correct format selected
WebP/AVIF tested where appropriate
JPEG/PNG fallback available where necessary
srcset configured for responsive images
sizes accurately reflects rendered width
width and height attributes included
Below-the-fold images use loading="lazy"
LCP image is not lazy-loaded
LCP image uses fetchpriority="high"
Compressed at actual required dimensions
Screenshots/UI graphics remain sharp
Network requests verified in DevTools
LCP measured after optimization
Real-user performance checked when available

Conclusion

Combine format, dimensions, responsiveness, and priority

Modern image optimization is not simply "convert JPG to WebP." The real performance improvement comes from combining several decisions:

Correct format + Correct dimensions + Responsive srcset + Accurate sizes + Correct compression + Correct loading priority = Efficient image delivery

Use AVIF and WebP where they make sense, keep reliable fallbacks, and let the browser choose between appropriately sized candidates with <picture>, srcset, and sizes.

Most importantly, do not lazy-load your LCP hero image. Critical images need to be discoverable and prioritized early, while images below the fold can be deferred with native loading="lazy".

The objective isn't to make images tiny. It's to deliver the smallest image that looks correct, at exactly the time and resolution the user needs it.

How much image payload could your website eliminate if every image were delivered in the correct format, dimensions, and loading priority?