Addy Osmani is an engineering manager working on Google Chrome. His team focuses on speed tools and frameworks, helping keep the web fast. Devoted to the …
More about Addy ↬
Images have also been a key part of the web. They communicate ideas instantly, but they are also a lot heavier than text to load. This means that it’s essential to get loading and displaying them right, if you want to give your users a fantastic first impression. An excerpt from
Addy’s new book
(also, check a
free PDF preview
).
The humble
element has gained some superpowers over the years. Given how central it is to image optimization on the web, let’s catch up on what it can do and how it can help improve user experience and the
Core Web Vitals
. I’ll be covering image optimization in more depth in Smashing Magazine’s
new book on Image Optimization
.
Avoid images causing network contention with other critical resources like CSS and JS. While not render-blocking, they can indirectly impact render performance.
Note
:
Modern image components that build on
, like
Next.js
(for React) and
Nuxt image
(for Vue) try to bake in as many of these concepts as possible by default. We’ll cover this later. You can of course also do this manually just using the
element directly. If using 11ty for your static sites, try the
11ty high-performance blog template
.
How Do Images Impact User Experience And The Core Web Vitals?
You may have heard of Core Web Vitals (CWV). It’s an initiative by Google to share unified guidance for quality signals that can be key to delivering a great user experience on the web. CWV is part of a set of
page experience signals
Google Search will be evaluating for ranking. Images can impact the CWV in a number of ways.
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
)
In many modern web experiences, images tend to be the largest visible element when a page completes loading. These include Hero images, images from carousels, stories and banners.
Largest Contentful Paint
(LCP) is a Core Web Vitals metric that measures when the largest contentful element (images, text) in a user’s viewport, such as one of these images, becomes visible.
This allows a browser to determine when
the main content of the page has finished rendering
. When an image is the largest contentful element, how slowly that image loads can impact LCP. In addition to applying image compression (e.g using
Squoosh
,
Sharp
,
ImageOptim
or an
image CDN
), and using a modern image format, you can tweak the
element to serve the most appropriate responsive version of an image or lazy-load it.
Largest Contentful Paint displayed in the WebPageTest filmstrip. (
Large preview
)
Layout shifts can be distracting to users. Imagine you’ve started reading an article when all of a sudden elements shift around the page, throwing you off and requiring you to find your place again.
Cumulative Layout Shift
(CLS, a Core Web Vitals metric) measures the instability of content. The most common causes of CLS include images without dimensions (see below) which can push down content when they load and snap into place; ignoring them means the browser may not be able to reserve sufficient space in advance of them loading.
It’s possible for images to block a user’s bandwidth and CPU on page load. They can get in the way of how critical resources are loaded, in particular on slow connections and lower-end mobile devices leading to bandwidth saturation.
First Input Delay
(FID) is a Core Web Vitals metric that captures a user’s first impression of a site’s interactivity and responsiveness. By reducing main-thread CPU usage, FID can also be reduced.
More after jump! Continue reading below ↓
Lighthouse
In this guide, we will be using Lighthouse to identify opportunities to improve the Core Web Vitals.
Lighthouse
is an open-source, automated tool for improving the quality of web pages. You can find it in the
Chrome DevTools
suite of debugging tools and run it against any web page, public or requiring authentication. You can also find Lighthouse in
PageSpeed Insights
,
CI
and
WebPageTest
.
Keep in mind that Lighthouse is a lab tool. While great for looking at opportunities to improve your user experience, always try to consult
real-world data
for a complete picture of what actual users are seeing.
The Basics
To place an image on a web page, we use the
element. This is an empty element — it has
no closing tag
— requiring a minimum of one attribute to be helpful:
src
, the source. If an image is called
donut.jpg
and it exists in the same path as your HTML document, it can be embedded as follows:
To ensure our image is accessible, we add the
alt
attribute
. The value of this attribute should be a textual description of the image, and is used as an alternative to the image when it can’t be displayed or seen; for example, a user accessing your page via a screen reader. The above code with an
alt
specified looks as follows:
Next, we add
width
and
height
attributes
to specify the width and height of the image, otherwise known as the image’s dimensions. The dimensions of an image can usually be found by looking at this information via your operating system’s file explorer (
Cmd + I
on macOS).
When
width
and
height
are specified on an image, the browser knows how much space to reserve for the image until it is downloaded. Forgetting to include the image’s dimensions
can cause layout shifts
, as the browser is unsure how much space the image will need.
Modern browsers now set the default aspect ratio of images based on an image’s
width
and
height
attributes, so it’s valuable to set them to prevent such layout shifts.
Identify The Largest Contentful Paint Element
Lighthouse has a “Largest Contentful Paint element” audit that identifies what element was the largest contentful paint. Hovering over the element will highlight it in the main browser window.
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
)
If this element is an image, this information is a useful hint you may want to optimize the loading of this image. You might also find this helpful
LCP Bookmarklet
by Annie Sullivan useful for quickly identifying the LCP element with a red rectangle in just one click.
Note:
The Largest Contentful Paint element candidate
can change
through the page load. For this reason, it’s valuable to not just look at what synthetic tooling like Lighthouse may say, but also consult what
real users
see.
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.
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
)
Identify Layout Shifts From Images Without Dimensions
To
limit
Cumulative Layout Shift being caused by images without dimensions, include width and height size attributes on your images and video elements. This approach ensures that the browser can allocate the correct amount of space in the document while the image is loading. Lighthouse will highlight 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
)
What about switching image resolution? A standard
only allows us to supply a single source file to the browser. But with the
srcset
and
sizes
attributes, we can provide many additional source images (and hints) so the browser can pick the most appropriate one. This allows us to supply images that are smaller or larger.
The
srcset
attribute defines the set of images the browser can select from, as well as the size of each image. Each image string is separated by a comma and includes:
a source filename (
donut-400w.jpg
);
a space;
and the image’s intrinsic width specified in pixels (
400w
), or a pixel density descriptor (
1x
,
1.5x
,
2x
, and so on).
The
sizes
attribute specifies a set of conditions, such as screen widths, and what image size is best to select when those conditions are met. Above, (
max-width:640px
) is a media condition asking “if the viewport width is 640 pixels or less,” and
400px
is the width of the slot, the image is going to fill when the media condition is true. This typically corresponds to the page’s responsive breakpoints.
Device Pixel Ratio (DPR) / Pixel Density Capping
Device Pixel Ratio (DPR) represents how a CSS pixel is translated to physical pixels on a hardware screen.
High resolution
and retina screens use more physical pixels to represent CSS pixels for imagery that is sharper and has more detailed visuals.
The human eye may not be capable of distinguishing the difference between images that are a 2x-3x DPR vs. an even higher resolution. Serving overly high DPR images is a common problem for sites leveraging
and a suite of image sizes.
Twitter capped their image fidelity at 2x, resulting in 33% faster timeline image loading times. (
Large preview
)
It may be possible to use DPR-capping to serve your users an image at a 2x or 3x fidelity to prevent large image payloads. Twitter
capped
their image fidelity at 2x, resulting in 33% faster timeline image loading times. They found that 2x was a sweet spot of both good performance wins with no degradation in quality metrics.
Note:
This approach to DPR-capping is currently not possible if using “w” descriptors.
Lighthouse includes a number of image optimization audits for helping you understand if your images could be better compressed, delivered in a more optimal modern image format, or resized.
Lighthouse’ notes on appropriately-sized images. (
Large preview
)
Even those images which are responsive (that is, sized relative to the viewport) should have a width and height set. In modern browsers, these attributes establish an aspect ratio that helps prevent layout shifts, even if the absolute sizes are overridden by CSS.
When not using an image CDN or framework, I like to use
responsivebreakpoints.com
to determine the optimal image breakpoints and generate
srcset code for my responsive images.
Serving Modern Image Formats
Art direction allows us to serve different images depending on a user’s display. While responsive images load different sizes of the same image, art direction can load very different images based on the display.
The browser can choose which image format to display using the
element. The
element supports multiple
elements and a single
element, which can reference sources for different formats including AVIF, WebP, and eventually JPEG XL.
In this example, the browser will begin to parse the sources and will stop when it has found the first supported match. If no match is found, the browser loads the source specified in
as the fallback. This approach works well for serving any modern image format not supported in all browsers. Be careful with
ordering
elements
as order matters. Don’t place modern sources after legacy formats, but instead put them before. Browsers that understand it will use them and those that don’t will move onto more widely supported frameworks.
Understanding the myriad of image format options out there today can be a confusing process, but you may find Cloudinary’s
comparison of modern image formats
helpful:
You may also find Malte Ubl’s
AVIF and WebP quality settings picker
useful for selecting quality settings to match the quality of a JPEG at a particular given quality setting.
Identify Images That Could Be Served In A More Modern Format
Lighthouse (below) highlights potential savings from serving images in a next-generation format.
Note:
We have an
open issue
to better highlight the potential savings for AVIF in Lighthouse.
You might also find value in using image auditing tools such as
Cloudinary’s image analysis tool
for a deeper look at image compression opportunities for all the images on a page. As a bonus, you can download compressed versions of suggested image formats such as WebP:
I also enjoy using
Squoosh
for its support of bleeding-edge formats, such as JPEG XL as it offers a low-friction way to experiment with modern formats outside of a CLI or CDN.
There are multiple ways to approach sizing issues as both srcset and sizes are both usable on
and
. when in doubt, use
with srcset/sizes for single images that have a simple layout. Use
for serving multiple formats, complex layout and art direction.
Chrome DevTools allows you to disable modern image formats (
demo
), like WebP, AVIF or JPEG XL, to test differing fallbacks for them in the
Rendering
panel:
Chrome DevTools allows you to test differing fallbacks for WebP, AVIF or JPEG XL in the Rendering panel. (
Large preview
)
An alternative to manually handling image format selection using
is to rely on the
accept header
. This is sent by the client, allowing the server to deliver an image format that is the best fit for the user. CDNs such as
Akamai
,
Cloudinary
and
Cloudflare
support it.
Image Lazy Loading
What about offscreen images that are not visible until a user scrolls down the page? In the example below, all the images on the page are “eagerly loaded” (the default in browsers today), causing the user to download 1.1 MB of images. This can cause users’ data plans to take a hit in addition to affecting performance.
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
)
Using the
loading attribute
on
, we can control the behavior of image loading.
loading="lazy"
lazy-loads images, deferring their loading until they reach a calculated distance from the viewport.
loading="eager"
loads images right away, regardless of their visibility in the viewport.
eager
is the default so doesn’t need to be explicitly added (that is, just use
for eager loading).
Below is an example of lazy-loading an
with a single source:
With native
lazy-loading, the earlier example now downloads only about 90 KB of images! Just adding
loading="lazy"
to our offscreen images has a huge impact. You ideally want to lazy-load all images present
outside of the initial viewport
and avoid it for everything that is within the initial viewport.
An image gallery using native image lazy-loading on images outside of the viewport. As seen in the Chrome DevTools
Network
panel, the page now only downloads the bare minimum of images users need upfront. The rest of the images are loaded in as users scroll down the page. (
Large preview
)
Lazy loading also works with images that include
srcset
:
In addition to working on
srcset
, the loading attribute also works on
inside
:
The 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
)
See
CanIUse.com
for latest browser support for native image lazy-loading.
Request Your Image Early
Help the browser discover your LCP image early so that it can fetch and render it with minimal delay. Where possible, attempt to solve this by better minimizing the request chains to your LCP image so that the browser doesn’t need to first fetch, parse and execute JavaScript or wait for a component to render/hydrate to discover the image.
can be used with
to allow browsers to discover critical resources you want to load as soon as possible, prior to them being found in HTML.
If you are optimizing LCP, preload can help boost how soon late-discovered hero images (e.g such as those loaded by JavaScript or background hero images in CSS) are fetched. Preload can make a meaningful difference if you need critical images (like Hero images) to be prioritized over the load of other images on a page.
Note:
Use preload sparingly and
always
measure its impact in production. If the preload for your image is earlier in the document than it is, this can help browsers discover it (and order relative to other resources). When used incorrectly, preloading can
cause your image to delay
First Contentful Paint
(e.g CSS, Fonts) — the opposite of what you want. Also note that for such reprioritization efforts to be effective, it also
depends on servers prioritizing requests correctly
.
Preload can be used to fetch sources for an
of a particular format:
Note:
This approach only preloads the latest format where supported, however cannot be used to preload multiple supported formats as this would preload both of them.
Take care not to overuse preload (when each resource is considered important, none of them really are). Reserve it for critical resources which the browser’s preload scanner may not be able to quickly find organically.
Lighthouse suggests opportunities to apply this optimization in Lighthouse 6.5 and above.
See
CanIUse.com
for latest browser support for link rel=preload.
Image Decoding
Browsers need to decode the images they download in order to turn them into pixels on your screen. However, how browsers handle deferring images can vary. At the time of writing, Chrome and Safari present images and text together – synchronously – if possible. This looks correct visually, but images have to be decoded, which can mean text isn’t shown until this work is done. The
decoding
attribute on
allows you to signal a preference between synchronous and asynchronous image decoding.
decoding="async"
suggests it’s OK for image decoding to be deferred, meaning the browser can rasterize and display content without images while scheduling an asynchronous decode that is off the critical path. As soon as image decoding is complete, the browser can update the presentation to include images.
decoding=sync
hints that the decode for an image should not be deferred, and
decoding="auto"
lets the browser do what it determines is best.
(Updated: May 5, 2021)
: Decoding images is not
“async”
(not blocking raster) by default. If this were the case, too much content on the web would end up flashing on load. However, all decodes are off the main thread.
Note
:
See
CanIUse.com
for the latest browser support for the decoding attribute.
Placeholders
What if you would like to show the user a placeholder while the image loads? The
background-image
CSS property allows us to set background images on an element, including the
tag or any parent container elements. We can combine
background-image
with
background-size: cover
to set the size of an element’s background image and scale the image as large as possible without stretching the image.
Placeholders are often inline, Base64-encoded data URLs which are low-quality image placeholders (LQIP) or SVG image placeholders (SQIP). This allows users to get a very quick preview of the image, even on slow network connections, before the sharper final image loads in to replace it.
Note:
Given that Base64 data URLs can be quite long,
[svg text]
is denoted in the example above to improve readability.
With an inline SVG placeholder, here is how the example from earlier now looks when loaded on a very slow connection. Notice how users are shown a preview right away prior to any full-size images being downloaded:
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
)
There are a variety of modern solutions for image placeholders (e.g CSS background-color, LQIP, SQIP,
Blur Hash
, Potrace). Which approach makes the most sense for your user experience may depend on how much you’re attempting to offer a preview of the final content, display progress (e.g progressive loading) or just avoid a visual flash when the image finally loads in. I’m personally excited for
JPEG XL’s support
for full progressive rendering.
Ultimately including an inline data URL for your low-quality placeholder image that is served in the initial HTML within the
’s styles avoids the need for an additional network request. I’d consider a placeholder size of being <= 1-2KB as being optimal.
LCP will take into account the placeholder image’s intrinsic size
so ideally aim for the “preview” to match the intrinsic size of the real image being loaded.
Note:
There is an
open issue
to discuss factoring in progressive loading specifically into the Largest Contentful Paint metric.
Lazy-render Offscreen Content
Next, let’s discuss the
CSS
content-visibility
property, which allows the browser to skip rendering, layout and paint for elements until they are needed. This can help optimize page load performance if a large quantity of your page’s content is offscreen, including content which uses
elements.
content-visibility:auto
can
reduce
how much CPU work the browser has to do less work upfront, including offscreen image decoding.
section { content-visibility: auto; }
The
content-visibility
property can take a number of values, however,
auto
is the one that offers performance benefits. Sections of the page with
content-visibility: auto
get containment for layout, paint and style containment. Should the element be off-screen, it would also get size containment.
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
)
Browsers don’t paint the image content for content-visibility affected images, so this approach may introduce some savings.
You can pair
content-visibility
with
contain-intrinsic-size
which provides the natural size of the element if it is impacted by size containment. The
700px
value in this example approximates the width and height of each chunked section.
See
CanIUse.com
for latest browser support for CSS content-visibility.
Next.js Image Component
Next.js now includes an
Image component
with several of the above best practices baked in. The image component handles image optimization, generating responsive images (automating
) and lazy-loading in addition to many other capabilities. This is just one of the optimizations that has come out of the Chrome and Next.js teams
collaborating
with sites adopting it seeing up to a 60% better LCP and 100% better CLS.
In the below Next.js example, the standard
element is first used to load 3 donut images downloaded from Unsplash.
import Head from 'next/head'; export default function Index() { return (
When this page is loaded 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), they all load regardless of whether the user can see them and are likely not as optimized as they could be.
Loading images at these sizes is unnecessary, in particular if our user is on a mobile device. Let’s now use the Next.js image component instead. We import it in from ‘next/image’ and replace all our
references with
.
import Head from 'next/head'; import Image from 'next/image'; export default function Index() { return (
Next.js Image Component
); }
We can reload our page and take a second look at the DevTools network panel. Now only 1 image is being loaded by default (the only one visible in the viewport), it’s significantly smaller than the original (~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:
Next.js will optimize images dynamically upon request and store them in a local cache. The optimized image then gets served for subsequent requests until an expiration is reached.
Next.js can also generate several versions of the image to serve media to smaller screens at the right size. When loading the page under mobile emulation (a Pixel phone), an even smaller 16KB image gets served for our first image.
Next.js generates several versions of the image to serve media to smaller screens at the right size. (
Large preview
)
When a user scrolls down the page, the additional images are lazy-loaded in. Note how no additional configuration or tweaking was needed here — the component just did the right thing by default.
The performance optimizations offered by the Next.js image component can help improve Largest Contentful Paint. To learn more about the component, including the different layout modes it supports, check out the
Next.js documentation
. A component with similar capabilities is available for
Nuxt.js
.
What Are Examples Of Businesses Improving LCP Via Image Optimizations?
Vodafone
found
that a 31% improvement in LCP increased sales by 8%. Their optimizations to improve LCP included resizing their hero image, optimizing SVGs and using media queries to limit loading offscreen images.
Agrofy
found
that 70% improvement in LCP correlated to a 76% reduction in load abandonment. Their optimizations to LCP included a
2.5s saving
from switching their first large image from being behind JavaScript (client-side hydration) to being directly in the main HTML document.
Agrofy found that 70% improvement in LCP correlated to a 76% reduction in load abandonment. (
Large preview
)
French Fashion house
Chloè
used Link Preload to preload their 1x and 2x Hero images, which were previously bottlenecked by a render-blocking script. This improved their Largest Contentful Paint by 500ms based on Chrome UX Report data over 28 days.
Optimizations to Cumulative Layout Shift helped YAHOO! Japan increased its News page views per session by 15%. They determined shifts were caused after their hero images were loaded and snapped in for the first view. They used
Aspect Ratio Boxes
to reserve space before their image was loaded.
Optimizations to Cumulative Layout Shift helped YAHOO! Japan increased their News’s page views per session by 15%. (
Large preview
)
Lab Data Is Only Part Of The Puzzle. You Also Need Field Data.
Before we go, I’d love to share a quick reminder about the importance of looking at the image experience your real users might have. Lab tools like Lighthouse measure performance in a synthetic (emulated mobile) environment limited to page load, while field or
real-world data
(e.g. RUM,
Chrome UX Report
) are based on real users throughout the lifetime of the page.
It’s important to
check how realistic your lab results are
by comparing them against typical users in the field. For example, if your lab CLS is much lower than the 75th percentile CLS in the field, you may not be measuring layout shifts as real users are experiencing them.
CLS is measured during the
full lifespan of a page
, so user behavior (interactions, scrolls, clicks) can have an impact on the elements that are shifting. For images, lab CLS may not see an improvement from fixing missing image dimensions if the images happen to be far down a page requiring a scroll. This is just one place where it’s worthwhile consulting real-user data.
For LCP it is very possible that the
candidate element can change depending on factors such as load times
(the LCP candidate could initially be a paragraph of text and then a large hero image), personalization or even different screen resolutions. Lighthouse’s LCP audit is a good place to start, but do take a look at what real users see to get the full picture.
Whenever possible, try to
configure your lab tests
to reflect real-user access and behavior patterns. Philip Walton has an excellent guide on
debugging Web Vitals in the field
worth checking for more details.
Editorial Note: Addy’s Book on Image Optimization
We’re happy and honored to have teamed up with Addy to publish a dedicated
book on image optimization
, and the book is now finally here. With modern approaches to image compression and image delivery, current and emerging image formats, how browsers load, decode and render images, CDNs, lazy-loading, adaptive media loading and how to optimize for Core Web Vitals. Everything in one, single, 528-pages book.
Download a free PDF sample
(12MB).
Now that we’ve covered the foundations of the modern
tag, check out the
pre-release of the Image Optimization book
to get a deeper understanding of how images can impact performance and UX.
Throughout the
book
, we will cover advanced image optimization techniques that expand heavily on the above guidance, as well as how to best use elements like
and
to make your images on the web shine.