Why Image Loading Still Decides First Impressions

Text loads fast, but images carry the visual weight of a page. The <img> element has evolved considerably, and knowing what those changes are matters more than ever now that Core Web Vitals (CWV) are part of the page experience signals Google evaluates for ranking.

Getting images right directly influences the three core metrics:

  • Largest Contentful Paint (LCP): Request hero images early, use srcset with efficient modern formats, avoid wasting pixels by over-serving high-DPR files, and lazy-load offscreen imagery to reduce network contention.
  • Cumulative Layout Shift (CLS): Always declare width and height attributes. The browser uses them to reserve space before the image arrives.
  • First Input Delay (FID): Heavy images can saturate bandwidth and slow the main thread on lower-end devices. Keep them from blocking critical CSS and JavaScript.
Note: Modern React and Vue components like Next.js <Image> and Nuxt image try to apply these concepts by default. Doing it manually with the raw <img> element is still entirely viable. For 11ty static sites, the 11ty high-performance blog template is worth trying. These ideas are covered in depth in Smashing Magazine's book on Image Optimization.
Largest Contentful Paint
Images can impact the Core Web Vitals in a number of ways. E.g. LCP measures when the largest contentful element (images, text) in a user’s viewport, such as one of these images, becomes visible. (Large preview)

Images are often the largest visible element on a page after load, making them the primary LCP candidate. You can influence how quickly that element renders through compression, format choice, and responsive markup.

Largest Contentful Paint displayed in the WebPageTest filmstrip.
Largest Contentful Paint displayed in the WebPageTest filmstrip. (Large preview)

Layout instability frustrates users mid-read, and images without dimensions are the usual culprit. When a browser lacks them, it cannot reserve the necessary space before the image renders.

Cumulative Layout Shift
Generated using Layout Shift GIF Generator. You may also be interested in the CLS Debugger. (Large preview)

Auditing Opportunities with Lighthouse

Lighthouse is an open-source automated tool for improving web page quality, available within the Chrome DevTools suite, PageSpeed Insights, CI, and WebPageTest. It is a lab tool, so consult real-world field data alongside any audit results to understand the actual user experience.

The Anatomy of the <img> Element

The element is empty, so it has no closing tag, and it needs at least a src attribute to be relevant.

<img src="donut.jpg">

The alt attribute is its accessible counterpart, providing a textual substitute when the image cannot render or be perceived.

<img src="donut.jpg"
     alt="A delicious pink donut.">

You should also supply width and height attributes, which correspond to the image's intrinsic dimensions. Omitting them causes layout shifts as the browser cannot determine how much space to allocate. Modern browsers derive the default aspect ratio from these attributes, so setting them remains valuable even when CSS overrides the values for final presentation.

<img src="donut.jpg"
     alt="A delicious pink donut."
     width="400"
     height="400">

Finding the LCP Element and Layout Shifts

Lighthouse has a dedicated "Largest Contentful Paint element" audit. Hovering over the result highlights the element in the browser window, indicating whether an image is your primary optimization target. Annie Sullivan's LCP Bookmarklet is a quick one-click alternative. Note that the LCP candidate can change over the page load, so compare synthetic reports against field data.

Lighthouse’s LCP
To identify the Largest Contentful Paint, spot the LCP in the Lighthouse metric. It also shows up on hover in the DevTools Performance panel. (Large preview)

In the DevTools Elements panel, hovering over an image reveals both its rendered and intrinsic dimensions.

Chrome DevTools Elements panel
Hovering over an image in the Chrome DevTools 'Elements' panel will display the dimensions of the image as well as the image’s intrinsic size. (Large preview)

To address CLS, Lighthouse also flags images lacking explicit width and height attributes.

Lighthouse highlights images without a width and height
It's a good idea to always include width and height size attributes on your images and video elements. (Large preview)

Serving Responsive Assets

The default <img> accepts a single source file. The srcset and sizes attributes expand that to a set of candidates. The srcset attribute lists each file separated by a comma, paired with either its intrinsic width in pixels (400w) or a pixel density descriptor (2x). The sizes attribute then specifies media conditions, such as (max-width:640px), and the slot width the image should fill when those conditions are true.

<img src="donut-800w.jpg"
     alt="A delicious pink donut."
     width="400"
     height="400"
    
    >
From Speed at Scale with Katie Hempenius and I at Google I/O.
From Speed at Scale with Katie Hempenius and I at Google I/O. (Large preview)

DPR and Pixel Density Capping

Device Pixel Ratio (DPR) maps CSS pixels to physical pixels on high-resolution and retina displays. While these screens benefit from sharper images, the human eye cannot always distinguish between very high resolutions. Serving 4x or 5x images wastes bytes. Twitter capped fidelity at 2x and found a 33% improvement in timeline image loading with no loss in quality metrics. This capping strategy is not possible when using "w" descriptors.

Device Pixel Ratio
Twitter capped their image fidelity at 2x, resulting in 33% faster timeline image loading times. (Large preview)

Sizing and Compression Audits

Lighthouse includes audits to determine whether images can be resized, better compressed, or delivered in a more optimal modern format.

Image optimization audits in the Lighthouse
Lighthouse’ notes on appropriately-sized images. (Large preview)

Even responsive images need explicit dimensions; the aspect ratio derived from those attributes is what prevents layout shifts once the image loads. When working without a framework or image CDN, tools like responsivebreakpoints.com help calculate the optimal breakpoints and generate the corresponding srcset markup.

responsivebreakpoints.com which determines the optimal image breakpoints.
Responsive Breakpoints helps you determine the optimal image breakpoints and generate srcset code for responsive images. (Large preview)

Choosing the Right Delivery Strategy for Modern Formats

The <picture> element provides a way to serve different image formats depending on what the browser supports. It works by grouping multiple <source> elements with a single <img> fallback. When parsing, the browser selects the first source whose format it recognizes; if none match, it falls back to the file referenced in the <img> tag.

<picture>
    <source type="image/jxl">
    <source type="image/avif">
    <source type="image/webp">
    <source type="image/jpeg">
    <img src="puppy.jpg" alt="Cute puppy">
</picture>

Ordering of the <source> elements is critical. Modern formats such as AVIF or WebP should be listed first, followed by formats with broader legacy support. Listing newer formats first lets supporting browsers pick them up immediately, while older browsers naturally move down to the fallback.

Cloudinary's comparison page is a useful reference for navigating today's format landscape, and Malte Ubl's quality settings picker helps translate familiar JPEG quality levels into equivalent settings for AVIF and WebP.

Finding Conversion Opportunities

Lighthouse can flag images that would benefit from using a newer format. An open issue tracks improving the accuracy of the savings estimates for AVIF specifically. For deeper analysis, tools like Cloudinary's image analysis tool audit compression potential across all images on a page and provide downloadable optimized versions. Squoosh is also handy for experimenting with up-and-coming codecs such as JPEG XL without needing a command-line setup or CDN. For verification, Chrome DevTools' Rendering panel can simulate environments that don't support WebP, AVIF or JPEG XL. Up-to-date browser support details are tracked on CanIUse for WebP, AVIF and JPEG XL.

Cloudinary’s comparison of modern image formats.
There are plenty of format options for images. A handy overview on Cloudinary blog. (Large preview)

As a general rule for sizing: stick with <img> plus srcset/sizes for images with straightforward responsive sizing. Turn to <picture> when you're juggling multiple source formats, complicated responsive layouts, or responsive art direction like "because the layout viewport is really large, provide a landscape-cropped version instead of a portrait one."

Lighthouse highlights potential savings from serving images in a next-generation format.
(Large preview)

A simpler mechanism is the HTTP Accept header. When a browser sends this header automatically, an origin server can negotiate format per-request without markup changes. Several major content-delivery platforms, including Akamai, Cloudinary and Cloudflare, leverage this for format fallbacks or optimization ("Polish") workflows.

Cloudinary’s image analysis tool
Cloudinary’s image analysis tool. (Large preview)

Lazy-Loading Offscreen Images

By default, browsers start downloading every <img> on a page immediately (the "eager" mode), regardless of whether it's visible. A page stacked with hero or gallery images will therefore ship a huge number of bytes to the user on initial load, particularly if the images are below the fold.

An image gallery eagerly loading all the images it needs upfront, as shown in the Chrome DevTools Network panel. 1.1 MB of images have been downloaded, despite only a small number being visible when the user first lands on the page.
An image gallery eagerly loading all the images it needs upfront, as shown in the Chrome DevTools Network panel. 1.1 MB of images have been downloaded, despite only a small number being visible when the user first lands on the page. (Large preview)

The loading attribute changes that portrait. Marking an image as loading="lazy" instructs the browser to hold off fetching it until it gets near a calculated threshold close to the viewport, while loading="eager" is the default and doesn't require any explicit markup. Simply adding at the bottom images can drop page weight drastically — an example page carrying over 1.1 MB of images can drop to just 90 KB with lazy loading. Appending the attribute to images used across full <img> and <srcset>, and to images inside the <picture> element.

<img src="donut.jpg"
     alt="A delicious pink donut."
    
     width="400"
     height="400">
<img src="donut-800w.jpg"
     alt="A delicious donut"
     width="400"
     height="400"
    
    
    >
<!-- Lazy-load images in <picture>. 
<img> is the one driving image loading so <picture> and srcset fall off of that -->
<picture>
  <source media="(min-width: 40em)">
  <source>
  <img src="fallback.jpg">
</picture>

Applying lazy-loading to everything visible in the viewport initially is common practice. But careful with it — don't mark such images lazy, particularly if placeholders or transitions would trigger a late-paint or hurt Largest Contentful Paint. Best practice is to lazy-load offscreen images while eager-loading the rest.

You can see how your page is scored and where delays originate from the Lighthouse Opportunities list; it reports non-lazy images that could potentially be deferred with this attribute. Browser support against all major engines can be found on CanIUse.

Lighthouse Opportunities section lists any offscreen or hidden images on a page that can be lazy-loaded as well as the potential savings from doing so.
Lighthouse Opportunities section lists any offscreen or hidden images on a page that can be lazy-loaded as well as the potential savings from doing so. (Large preview)

Preloading: When and Why to Use It

An LCP image that exists in the HTML is usually discovered by the browser's preload scanner early in the document lifecycle. But if the hero is styled as a CSS background or gets injected into the DOM from a JavaScript component after page load, there will be much avoidable delay before that fetch can even start. This is a classic case for <link rel="preload">.

<link rel="preload" as="image" href="donut.jpg">

In preloading, your intent isn't to fetch more bytes, it's to initiate a request early. Add <link rel=preload as=image> and cite the URL. Native preload hints can shine in cases where LCP is critical, especially since it can also fall back to preloaded attributes. However — if your markup structure is such that the hero naturally shows early, then preload likely won't move the needle much. It's also useful to prioritize your main hero against tons of eager images that would otherwise compete for network bandwidth. But you must use preload sparingly: applied without discrimination, preloading everything will inflate your contention for bandwidth and may cause your LCP image to be delayed behind a festival of CSS and font preloads.

Preloading an image format used later inside <img> — and preloading a responsive image via imagesrcset/imagesizes attributes — gets fetched by the browser and works directly with resolution switching. But preloading can't anticipate whether an <picture> source falls back to another format; stateful media has to be about format-specific markup. Trying to preload both WebP and AVIF variants means both get fetched up front, and the preload response gets wasted payload and bandwidth. For responsive images, however, the browser picks the best match and fetches just that one.

preload for optimizing LCP.
(Large preview)
<link rel="preload" as="image" 
     href="donut.jpg" 
     imagesrcset="
        poster_400px.jpg 400w, 
        poster_800px.jpg 800w, 
        poster_1600px.jpg 1600w" 
    imagesizes="50vw">

At present, a couple extra things need watching here. When using preload, the fetchpriority attribute can prioritize it, and in some browsers, once fet signals allow the browser's preload scanner to make valuable adaptive decisions. Tooling like Lighthouse flags site-controlled preload opportunities and suggests where to apply it. Ideally, preloading should be in the early parts of the document. Be wary that many hosts and servers over bad prioritization between scripts, GC and fonts can neutralize preload wins — browser devtools let you check how resources later arrive relative to the actual page bottleneck. For browsers, native support of preload links has broad compatibility.

Critical: Preload isn't about doubling down, rather it's about providing strong initial discoverability for heroes the scanner can't otherwise see.

Preload in the Lighthouse.
(Large preview)

Decoding: Moving Pixels off the Critical Path

After an image arrives, decoding for screen is the final piece of work. In some browsers the presentation waits to show intrusive-up (an image full he could combine its painted slot synchronously first) before carrying you to more important things: text layout. The decoding attribute on <img> moves the decode — and the display it unlocks — back and forth along performance/quality tradeoffs.

<img src="donut-800w.jpg"
     alt="A delicious donut"
     width="400"
     height="400"
    
    
    
    >

decoding="async" gives the browser the suggestion to defer image decode work. This avoids image placeholder layout gaps or FOUC impacts: rasterization without images can continue, pixels delivered to the viewport when-ready. Conversely, decoding="sync" is a signal the decode should never be deferred (the presentation might arrive with text). The default is decoding="auto", where the engine makes a pragmatic call.

(Updated May 2021): Images aren't fully asynchronous during decode — that would trigger unstable flashing on loads — decode occurs on the compositing thread in browsers (usually a worklet) not main thread, but is not inherently part of view. See CanIUse for support.

Showing Placeholders and Low-Quality Previews

Offsetting an image shell for a viewport can feel jarring at how one perceived. Placeholder pixels can address both, since previews can be inline and instantly display, even on poor network conditions. A classic technique uses an inline SVG or Base64 Low-Quality Image Placeholder that's output in the HTML, wrapped as a CSS background-image on the <img>, paired with background-size: cover. It renders near-immediately, swapping the sharper file once arrived.

<img src="donut-800w.jpg"
     alt="A delicious donut"
     width="400"
     height="400"
    
    
    
    
     style="background-size: cover; 
            background-image:
              url(data:image/svg+xml;base64,[svg text]);">

The inline placeholder limits the page weight. Note that in the example, given the large string of a base64-encoded image, an SVG placeholder is used for readability.

Images loaded on a simulated slow connection, displaying a placeholder approximating the final image as it loads in. This can improve perceived performance in certain cases.
Images loaded on a simulated slow connection, displaying a placeholder approximating the final image as it loads in. This can improve perceived performance in certain cases. (Large preview)

The visual effect is night/day under slow connections: users see the expected image layout or a preview right away, instead of blank space while complex bytes travel toward rending. Options include LQIP — where the placeholder is heavily compressed original — vs cross-over: the more modern Blur Hash, the vector-based SQIP, a simple CSS background color, or a lightweight illustration that broadly accentuates composition. The choice between approaches may depend on the space you allocate for the preview and its fidelity, but even better news: JPEG XL brings out progressive decode in older experience memory—marking a forward promise for progressive whole-image rendering with presentation. The progressive JPEG pattern sees inline and real images working as with a fine optimization in the placeholder (between an empty shell and a real image). An LCP variant here uses the intrinsic size of the placeholder inside the hero element, matching dimensions as a bonus anchor. There's an effort to integrate progressive loading into the Largest Contentful Paint config, highlighted in an open issue: “Loading sources just enough so we factor progressive decode into LCP.”

Perceptual image loading methods from Gunther Brunner of CyberAgent.
Perceptual image loading methods from Gunther Brunner of CyberAgent. (Large preview)

Defer Offscreen Rendering With content-visibility

Beyond controlling when images load, you can also control when the browser actually renders them. The CSS content-visibility property tells the browser it can skip rendering, layout, and paint for an element until it scrolls into view. For pages with substantial offscreen content, this reduces upfront CPU work, including the decoding of offscreen images.

section {
  content-visibility: auto;
}

The value auto is what unlocks the performance gains. Elements with content-visibility: auto receive layout, paint, and style containment; when off-screen, they also get size containment.

Sections of the page with <code src=content-visibility:auto">
When chunking up a page into sections with content-visibility:auto, developers have observed a 7-10x improvement in rendering times as a result — note the reduction in rendering times above of 937ms to 37ms for a long HTML document. (Large preview)
section {
  content-visibility: auto;
  contain-intrinsic-size: 700px; 
}

Because affected images are not painted, this approach can deliver real savings. When size containment kicks in, pair the property with contain-intrinsic-size to supply the element's natural dimensions — in the example above, 700px approximates the width and height of each chunked section. Check CanIUse.com for current browser support.

The Next.js Image Component: Built-In Best Practices

Next.js ships an Image component that packages many of the optimizations covered earlier into a single API. It handles image optimization, generates responsive srcset sources, and lazy-loads by default. The component came out of collaboration between Chrome and Next.js teams, with adopting sites reporting up to 60% better Largest Contentful Paint and 100% better Cumulative Layout Shift.

The contrast with a plain <img> tag is stark. In a page loading three donut images, the standard markup pulls them all down immediately at full resolution.

import Head from 'next/head';
export default function Index() {
    return (
        <div>
            <Head>
                <title>Create Next App</title>
            </Head>

            <main>
                <div>
                    <img src="https://www.smashingmagazine.com/donut1.jpeg" alt="Donut" height={700} width={700} />
                    <img src="https://www.smashingmagazine.com/donut2.jpeg" alt="Donut" height={700} width={700} />
                    <img src="https://www.smashingmagazine.com/donut3.jpeg" alt="Donut" height={700} width={700} />
                </div>
            </main>
        </div>
    );
}
Image Component with Next.js
(Large preview)

Opening DevTools reveals the problem: the images total 8.4MB (325KB + 4.5MB + 3.6MB), all load even if the user never scrolls to them, and none are optimized for the viewing device.

with the DevTools network panel open, we see that our images are very large in size (325KB + 4.5MB + 3.6MB = 8.4MB in total).
(Large preview)

Swapping the tags is minimal work: import the component from next/image and replace each <img> with <Image>.

import Head from 'next/head';
import Image from 'next/image';

export default function Index() {
    return (
        <div>
            <Head>
                <title>Next.js Image Component</title>
            </Head>
            <main>
                <div>
                    <Image src="https://www.smashingmagazine.com/donut1.jpeg" alt="Donut" height={700} width={700} />
                    <Image src="https://www.smashingmagazine.com/donut2.jpeg" alt="Donut" height={700} width={700} />
                    <Image src="https://www.smashingmagazine.com/donut3.jpeg" alt="Donut" height={700} width={700} />
                </div>
            </main>
        </div>
    );
}

The network panel now tells a different story. Only the single image in the initial viewport loads. At roughly 33KB versus the original 325KB, it is also significantly lighter and served in the modern WebP format.

An image is being loaded by default (the only one visible in the viewport), it’s significantly smaller than the original (approx. 33KB vs 325KB) and uses a more modern format (WebP).
An image is being loaded by default (the only one visible in the viewport), it’s significantly smaller than the original (approx. 33KB vs 325KB) and uses a more modern format (WebP). (Large preview)

Note: The component optimizes images dynamically on request and caches them locally. Subsequent requests receive the optimized version until the cache expires.

Next.js also generates multiple sizes of each image. Under mobile emulation (a Pixel phone), the first image shrinks further to an even smaller 16KB file.

Next.js generates several versions of the image to serve media to smaller screens at the right size.
Next.js generates several versions of the image to serve media to smaller screens at the right size. (Large preview)

Scrolling down lazy-loads the remaining images. No extra configuration was needed—sensible behavior is built in.

Performance optimizations offered by the Next.js image component.

These optimizations directly improve LCP. For details on the layout modes and other options, consult the Next.js documentation. A similar component exists for Nuxt.js.

Business Results From Image Optimization

Real-world results back up the theory. Vodafone measured an 8% sales increase after improving LCP by 31%, using hero image resizing, SVG optimization, and media queries that limited offscreen image requests. Agrofy saw a 76% cut in load abandonment alongside a 70% LCP improvement; its biggest win was moving its first large image directly into the main HTML document instead of rendering it after client-side hydration, saving 2.5 seconds.

Agrofy found that 70% improvement in LCP correlated to a 76% reduction in load abandonment.
Agrofy found that 70% improvement in LCP correlated to a 76% reduction in load abandonment. (Large preview)

Fashion house Chloè tackled CLS separately. They used Link Preload to fetch 1x and 2x hero images that were previously blocked by a render-blocking script, shaving 500ms off LCP per Chrome UX Report data over 28 days. YAHOO! Japan increased news page views per session by 15% by fixing layout shifts caused by hero images that snapped into place after loading; Aspect Ratio Boxes reserved the space upfront.

Optimizations to Cumulative Layout Shift helped YAHOO! Japan increased their News’s page views per session by 15%.
Optimizations to Cumulative Layout Shift helped YAHOO! Japan increased their News’s page views per session by 15%. (Large preview)

Lab Results Only Tell Part of the Story

Lighthouse and other lab tools evaluate a synthetic, emulated mobile environment at page load. Real-world data—from RUM or the Chrome UX Report—captures what actual users experience across the full page lifetime. Comparing the two is essential: if lab CLS is far lower than the 75th percentile field CLS, chances are you are missing shifts that real users encounter.

CLS is measured throughout a page's life, so scrolling and interaction matter. A missing image dimension won't show up in lab CLS if that image sits below the fold and requires user action to reach. Similarly, the LCP candidate element can change based on load timing (a text block may initially qualify before a hero image arrives) or on personalization and viewport. Dive into what real users see rather than relying on the lab audit alone.

Configure lab tests to approximate real access patterns where possible. Philip Walton's guide on debugging Web Vitals in the field is an excellent starting point.

Further Reading

Smashing Editorial