Image optimization for Core Web Vitals: a developer checklist
Images affect two of the three Core Web Vitals directly. The hero image is often the Largest Contentful Paint (LCP) element, and images without reserved space are a common cause of Cumulative Layout Shift (CLS). For most pages the fix is a short list:
- Put the LCP image in the HTML, never lazy-load it, and give it
fetchpriority="high". - Preload it only when the browser can't find it in the markup (CSS backgrounds, images injected by JavaScript).
- Serve the right size with
srcsetand an accuratesizes. - Set
widthandheight(or CSSaspect-ratio) on every image so the browser reserves space. - Serve AVIF or WebP with a fallback, and compress the files.
- Judge the result by field data (Chrome UX Report), not only by a Lighthouse run.
The rest of this page explains each step, with the markup and the Next.js next/image equivalent.
Last updated:
What are the Core Web Vitals thresholds?
Google's Web Vitals overview sets the "good" thresholds and says to measure them at the 75th percentile of page loads, split by mobile and desktop. The "poor" boundaries below come from the Search Console Core Web Vitals report.
| Metric | Good | Poor | How images affect it |
|---|---|---|---|
| LCP (loading) | 2.5 s or less | over 4 s | Directly, when an image is the largest element in the viewport |
| CLS (visual stability) | 0.1 or less | over 0.25 | Directly, when an image loads without reserved space |
| INP (responsiveness) | 200 ms or less | over 500 ms | Rarely; INP measures interactions, not loading |
On search, Google's Core Web Vitals documentation says it highly recommends good Core Web Vitals "for success with Search" and that page experience aligns with what its core ranking systems reward. It does not promise a ranking change from fixing a metric, so treat the work as a user-experience fix first.
How do I find the LCP element?
Run the page through PageSpeed Insights. The lab section names the LCP element, and the field section (when the page has enough traffic) shows how real visitors experience it. In Chrome DevTools, the Performance panel marks LCP on the timeline and highlights the element.
Per web.dev's LCP definition, the candidates are <img> elements, <image> inside SVG, video posters (or first frame), elements with a CSS url() background image, and block-level text. If your LCP element is a heading, image work won't move LCP much; look at fonts and server response time instead.
Why is my LCP image slow?
web.dev's Optimize LCP guide splits LCP into four parts that add up to the total:
- Time to first byte: until the HTML starts arriving.
- Resource load delay: from TTFB until the image request starts. This should be close to zero.
- Resource load duration: downloading the image. Smaller files shrink this.
- Element render delay: from download finished until the image is painted.
Compression only helps the third part. The guide's ideal split puts TTFB and load duration at about 40% each and the two delays under 10% each; if load delay is large, the browser is discovering the image late or requesting it at low priority. The guide warns that shrinking the file without fixing the delays can just move the time into render delay, so check where the time goes before re-encoding images.
How do I make the LCP image load first?
Make it discoverable, don't defer it, and raise its priority.
<img
src="/hero-1200.webp"
srcset="/hero-640.webp 640w, /hero-1200.webp 1200w, /hero-1920.webp 1920w"
sizes="100vw"
width="1920"
height="1080"
alt="Product dashboard on a laptop"
fetchpriority="high"
/>
Discoverable means the src or srcset is in the server-rendered HTML, so the browser's preload scanner can request it before CSS and JavaScript run. An image that a carousel script inserts, or one set as a CSS background-image, is found much later.
Don't lazy-load it. web.dev's lazy-loading guide is explicit: don't lazy-load images likely to be in the viewport at load, especially LCP images. eager is the HTML default, so the fix is usually to remove loading="lazy" from the hero, often added by a component or CMS that applies it to every image.
Raise its priority. According to web.dev's Fetch Priority article, images start at low priority and are only boosted after layout. fetchpriority="high" lets the LCP image start high. Use it on one or two images at most; the Optimize LCP guide notes it stops helping when overused. Support listed there: Chrome and Edge 102, Firefox 132, Safari 17.2. Browsers without support ignore the attribute.
When do I need a preload?
Only when the image isn't in the HTML. Preloading responsive images recommends avoiding preload for images the preload scanner can already find. For a CSS background or a script-injected image:
<link
rel="preload"
as="image"
imagesrcset="/hero-640.webp 640w, /hero-1200.webp 1200w, /hero-1920.webp 1920w"
imagesizes="100vw"
fetchpriority="high"
/>
Two caveats from the same article. Preload has no format fallback: several typed preloads may all download, so preload one format only. And it can't express <picture> art direction, so each breakpoint needs its own preload with non-overlapping media ranges.
How do I size images with srcset and sizes?
srcset lists the available widths; sizes tells the browser how wide the image will be displayed so it can choose. If sizes is missing, MDN's img reference says it defaults to 100vw. An image shown in a 400 px sidebar column then downloads as if it filled the screen.
<img
srcset="/card-400.webp 400w, /card-800.webp 800w, /card-1200.webp 1200w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
src="/card-800.webp"
width="1200"
height="800"
alt="Team photo"
loading="lazy"
/>
Write sizes from your real CSS breakpoints, then check in DevTools (Network panel, or currentSrc on the element) that phones get the small file. SnapCompress recommendation: include widths up to about 2x the largest display size, for high-density screens. For below-the-fold images, MDN documents sizes="auto", which uses the laid-out width; it is only valid together with loading="lazy".
If you are producing the variants by hand, SnapCompress Resize creates each width in the browser without uploading the file.
How do I stop images from causing layout shift?
Give every image its intrinsic width and height, and let CSS do the scaling:
img {
max-width: 100%;
height: auto;
}
Optimize CLS explains why this works: modern browsers derive a default aspect ratio from the width and height attributes (Chrome shows it as aspect-ratio: auto 640 / 360) and reserve that space before the file arrives. The attributes don't fix the rendered size; they only supply the ratio. All srcset candidates should share the same ratio.
When you can't know the dimensions, for example a CMS image in a fixed card, reserve the space with CSS instead:
.card-media {
aspect-ratio: 16 / 9;
}
.card-media img {
width: 100%;
height: 100%;
object-fit: cover;
}
For art direction with <picture>, where the mobile crop has a different ratio, put width and height on each <source> as well. Also watch for space you didn't reserve around images: cookie banners, ad slots, and late-loading embeds above the image shift it just as much.
Which image format should I serve?
AVIF or WebP for photos, with a JPEG or PNG fallback through <picture>:
<picture>
<source srcset="/hero.avif" type="image/avif" />
<source srcset="/hero.webp" type="image/webp" />
<img src="/hero.jpg" width="1920" height="1080" alt="Product dashboard on a laptop" fetchpriority="high" />
</picture>
MDN's picture reference explains that the browser skips any <source> whose type it doesn't support and uses the <img> as the fallback; width, height, alt, loading, and fetchpriority go on that <img>. Support on caniuse when this page was revised: WebP about 97% of global usage (Safari since 16), AVIF about 96% (Safari since 16.4, Chrome 85, Firefox 93, Edge 121).
Format savings depend on the image, so I don't quote a percentage here. For when to use JPEG, PNG, WebP, or AVIF, see JPEG vs PNG vs WebP. SnapCompress can produce WebP with Convert; it does not output AVIF, so generate AVIF with your build pipeline or image CDN.
How much should I compress the images?
There is no file-size number that guarantees good LCP; it depends on the server, the network, and the other three LCP parts. Compress until you can see artifacts at the displayed size, then step back.
SnapCompress recommendation: for photos, start at the quality default of your encoder, compare the output at 100% zoom against the original, and only go lower if you can't see a difference. Strip metadata you don't need, and never ship a 4,000 px camera original into a 1,200 px slot; resizing usually saves more than any quality setting.
SnapCompress Compress does this in the browser: it re-encodes JPEG, PNG, and WebP in the same format (HEIC and DNG come out as JPEG), with nothing uploaded. For the full workflow see how to compress images. Measured file-size results per quality setting will be published in the compressor benchmark.
Below the fold, add loading="lazy" so those images don't compete with the LCP image for bandwidth.
What changed in next/image in Next.js 16?
If you use Next.js, the Image component reference covers most of the above, with a few traps:
priorityis deprecated as of Next.js 16, in favor of a newpreloadprop. The docs add that in most cases you should useloading="eager"orfetchPriority="high"instead ofpreload.preloadinserts a<link>in the head and is meant for cases like the one above, where the image can't be found early.loadingdefaults tolazyinnext/image(the HTML default iseager), so the LCP image must opt out.widthandheightare required unless the image is statically imported or usesfill; withfill, the parent element needs a size.- Without
sizes, Next.js generates only a 1x/2xsrcsetfor a fixed-width image. Withsizes, it generates the full set of widths. - The
images.formatsconfig defaults to['image/webp']. Adding'image/avif'first serves AVIF to browsers that accept it. The docs still recommend WebP for most cases and note AVIF takes longer to encode, so the first uncached request is slower.
import Image from 'next/image'
import hero from './hero.jpg' // static import supplies width and height
export function Hero() {
return (
<Image
src={hero}
alt="Product dashboard on a laptop"
sizes="100vw"
loading="eager"
fetchPriority="high"
/>
)
}
This site uses formats: ['image/avif', 'image/webp'] with a one-year minimumCacheTTL in next.config.ts, which keeps the slower AVIF encode to the first request per size.
How do I measure the result?
Use field data to decide whether you have a problem and lab data to debug it. web.dev's lab and field data article explains why they disagree.
- Field data: PageSpeed Insights shows Chrome UX Report data for the previous 28 days at the 75th percentile. If the URL has too little traffic, it falls back to origin-level data, or shows none. The Search Console Core Web Vitals report groups similar URLs, and its "Start Tracking" validation also runs over 28 days, so a fix takes weeks to show.
- Lab data: Lighthouse (in PageSpeed Insights or DevTools) loads the page once on a simulated mid-tier phone and network. It is reproducible, which makes it right for checking whether the LCP image now starts early, but its score is not what Google uses for Core Web Vitals.
Field LCP can differ from lab LCP for reasons unrelated to images: a different LCP element on different screen sizes, warm caches for returning visitors, back/forward cache restores, and users who scroll before load finishes. If lab looks good and field doesn't, segment by device first.
Sources
- web.dev, Web Vitals, accessed 2026-10-08
- web.dev, Largest Contentful Paint (LCP), accessed 2026-10-08
- web.dev, Optimize Largest Contentful Paint, accessed 2026-10-08
- web.dev, Optimize Cumulative Layout Shift, accessed 2026-10-08
- web.dev, Interaction to Next Paint (INP), accessed 2026-10-08
- web.dev, Browser-level image lazy loading, accessed 2026-10-08
- web.dev, Optimize resource loading with the Fetch Priority API, accessed 2026-10-08
- web.dev, Preload responsive images, accessed 2026-10-08
- web.dev, Why lab and field data can be different, accessed 2026-10-08
- MDN, img element, accessed 2026-10-08
- MDN, picture element, accessed 2026-10-08
- Can I use, WebP and AVIF, accessed 2026-10-08
- Next.js, Image component reference, accessed 2026-10-08
- Google Search Central, Core Web Vitals and Google search results, accessed 2026-10-08
- Google, About PageSpeed Insights, accessed 2026-10-08
- Search Console Help, Core Web Vitals report, accessed 2026-10-08
Further reading
- Guides
How to Compress an Image Without Visible Quality Loss
Compress a photo or screenshot step by step: resize first, pick the right format, then lower quality until you see a difference. Free, in your browser.
Read - Guides
JPEG vs PNG vs WebP vs AVIF: Which Image Format Should You Use?
When to use JPEG, PNG, WebP, or AVIF: browser support from caniuse, a picture element fallback example, and a decision table for web and sharing.
Read - Guides
Image Resizing Best Practices: Which Pixel Size to Use
What pixel size to resize to for websites, email, and online stores, why to resize before compressing, and how to avoid upscaling and blur.
Read