All guidesPerformance

Images and Core Web Vitals: How to Fix a Slow LCP

Most failing LCP scores are an image problem. How to identify the element, why preloading beats compressing, and the lazy-loading mistake that quietly costs a second.

July 26, 20268 min read
A stopwatch resting on a laptop keyboard beside a monitor displaying a performance waterfall chart

Open any site with a failing Core Web Vitals assessment and there is roughly a four-in-five chance the culprit is a single image. Not the whole image strategy — one file, usually the hero, usually large, usually discovered late by the browser for a reason nobody on the team knows about.

This is good news, because it means LCP is generally fixable in an afternoon rather than a quarter. The catch is that the obvious fix — "compress the image" — addresses the smaller half of the problem. As of July 2026 the bigger half is nearly always when the browser learns the image exists.

Identify the element before touching anything

Every LCP investigation that goes badly starts with someone optimising the wrong file.

Run the URL through PageSpeed Insights and read the diagnostics section, which names the LCP element outright. Then confirm in Chrome DevTools: open the Performance panel, record a load with network throttling on, and look for the LCP marker in the timings track. Clicking it highlights the element in the DOM.

Two things to watch for. First, the LCP element differs by viewport — a mobile visitor's largest element is frequently not the desktop one, and mobile is where the scores fail. Second, lab tools measure one load on one connection from one location, while Search Console's Core Web Vitals report shows the 75th percentile of real visits over 28 days. Lab tells you the mechanism; field tells you whether it matters. Optimising a lab score that field data says is already fine is a common way to spend a week on nothing.

The four components of an image LCP

Google's own web.dev guidance on optimising LCP breaks the metric into phases, and the framing is genuinely useful because it tells you which fix applies:

Phase What it is Typical cause when it dominates The fix
Time to first byte Server responds Slow backend, no CDN, cold cache Caching, CDN, server work
Resource load delay Gap between page start and image request Lazy loading, CSS background, JS carousel, no preload Preload, fetchpriority, plain <img> in HTML
Resource load duration Actual download Oversized file, no responsive variants Compression, correct dimensions, modern format
Element render delay Download finished to paint Render-blocking CSS/fonts, hydration Reduce blocking resources

The instinct is to attack load duration, because that is the phase compression addresses. In practice resource load delay is where the seconds hide, and it is invisible in a file-size audit. An image the browser does not know about cannot download, no matter how small it is.

Fix discovery first

The browser's preload scanner reads the raw HTML before the main parser finishes and starts fetching images it finds. Anything that hides your hero image from that scanner costs you the whole discovery window.

CSS background images are invisible to it. A background-image rule in an external stylesheet is only discovered after the CSS is downloaded and parsed and the element is matched. If your hero is a CSS background, that alone can be a second. Move it to a real <img> element, or preload it explicitly.

JavaScript carousels are worse. If the first slide is injected by a client-side component, the image cannot be requested until the bundle downloads, parses and executes. The first slide should be plain HTML in the server response; let the carousel take over afterwards.

loading="lazy" on the LCP image is self-sabotage. This is now the most common LCP regression there is, because "lazy load all images" reads like a best practice and is applied globally by plugins and by well-meaning developers. A lazy image waits for layout and an intersection check before its request starts. Lazy loading is correct for below-the-fold images and actively harmful above it.

For the image you know is the LCP, be explicit:

<img src="/img/hero-1200.webp"
     srcset="/img/hero-600.webp 600w, /img/hero-1200.webp 1200w, /img/hero-1800.webp 1800w"
     sizes="(max-width: 768px) 100vw, 1200px"
     width="1200" height="675"
     alt="Locksmith cutting a replacement key at a mobile workbench"
     fetchpriority="high" decoding="async">

No loading attribute — eager is the default and what you want. fetchpriority="high" tells the browser this beats other images and non-critical scripts. And width/height reserve the space, which protects your Cumulative Layout Shift score at the same time.

Then fix the bytes — and the dimensions

Once discovery is clean, size matters. Two mistakes dominate.

Serving desktop dimensions to phones. A 2400 px-wide hero on a 390 px screen wastes roughly 97 percent of the pixels it transferred. Responsive srcset with an accurate sizes attribute is the fix, and the accuracy of sizes is where it goes wrong: it describes the rendered CSS width of the image, not the file width. Get it wrong and the browser dutifully picks a candidate far larger than needed. Chrome DevTools will tell you which candidate was chosen if you hover the img in the Elements panel.

Shipping generator or camera output directly. An unresized DALL·E or camera file can be several megabytes. AI-generated images are a particular offender here because they arrive as large PNGs and look finished, so nobody resizes them. Downscale to the maximum size you actually display, then encode. Format choice matters too, though less than dimensions — our WebP vs AVIF vs JPEG comparison covers where each earns its place.

A useful rule of thumb: a hero image on a content site should land under about 150 KB after resizing and encoding. If yours is over 400 KB, dimensions are almost certainly the reason rather than the encoder.

What to preload, and what not to

<link rel="preload" as="image"> in the <head> is the strongest tool available for load delay, and the most frequently misused. Preloading tells the browser to fetch something at high priority immediately, which is exactly right for one resource and counterproductive for six — every preload competes with the others for the same connection.

Preload the LCP image only, and only when you cannot fix discovery structurally. If the image is already a plain <img> high in the HTML with fetchpriority="high", the preload adds nothing. If it is a CSS background you cannot easily change, preload with imagesrcset and imagesizes matching your CSS breakpoints so you do not preload the wrong variant.

Also worth knowing: preloading a font that renders your headline can improve LCP when the LCP element is text, and worsen it when the element is an image competing for bandwidth. There is no universal answer — measure before and after.

Audit your sizes attribute — it is wrong more often than not

This deserves its own pass because it is invisible, common, and costs real bandwidth on exactly the devices least able to spare it.

The sizes attribute tells the browser how wide the image will be rendered, so it can choose a candidate from srcset before layout has happened. It is a promise about CSS, written in HTML, and nothing validates it. When the promise is wrong, the browser picks accordingly and you get a 1600 px file on a 390 px screen with no error anywhere.

The classic failure is a default sizes="100vw" left in place on an image that actually renders in a three-column grid at 380 px. The browser reserves for full viewport width and downloads the largest candidate.

To check: open DevTools, select the img in the Elements panel, and look at the currentSrc property — that is the candidate the browser actually chose. Compare it with the rendered box size shown in the layout overlay. If the chosen file is more than about twice the rendered width in CSS pixels, your sizes is lying.

Fix it by writing the value to match your real breakpoints:

sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 380px"

Frameworks that generate this for you generally get it right when you tell them the layout — Next.js requires sizes on fill images for exactly this reason, and omitting it there is a documented performance footgun.

The two metrics that travel with LCP

Fixing images tends to move all three Core Web Vitals at once, which is worth knowing so you measure the right things afterwards.

Cumulative Layout Shift improves the moment you add width and height (or an aspect-ratio rule) to every image. Without them the browser cannot reserve space, so content jumps as each file arrives. This is the cheapest CLS fix that exists and it is a two-attribute change.

Interaction to Next Paint improves indirectly. Every image the browser decodes occupies the main thread briefly, so a page that downloads forty full-size images competes with your own event handlers. Correct lazy loading below the fold and correctly sized files reduce that contention.

The upshot is that "fix the images" is usually the highest-leverage single project on a failing Core Web Vitals assessment, because one body of work touches all three metrics.

Where this fits alongside the rest of image SEO

Performance work is satisfying because the numbers move the same day. It is worth being honest about what it does and does not achieve for search.

LCP is a real but modest ranking input, and Google has repeatedly said content relevance dominates. Meanwhile the things Google's image documentation names first — descriptive filenames, genuine alt text, images near relevant text, valid structured data — are what determine whether your images appear in image search at all. A fast page full of IMG_2291.jpg still surfaces for nothing. The image SEO checklist orders these by leverage, and EXIF metadata covers the layer inside the file itself.

The split is worth internalising because it tells you what to automate. Delivery concerns — format, dimensions, lazy loading, preload — are configured once in your pipeline and then apply to every image forever. Descriptive concerns need a per-file human decision that no build step can make for you, which is exactly why they are the ones left undone at volume.

SEOpix closes that second gap by generating the filename, alt text and EXIF block with the image, so what enters your optimised pipeline is already described. Your build handles the bytes; the metadata is not a separate project you never get to.

Fix the lazy-load attribute on your hero this afternoon — it is usually a one-line change and often the single largest win available. Then, if per-file metadata is the part your team keeps skipping, ten images a month are free and the pricing page covers real volume.

Frequently asked questions

What is Largest Contentful Paint measuring?+

The time from when the page starts loading until the largest visible element in the viewport finishes rendering. On most pages that element is an image or a video poster frame; on text-heavy pages it can be a heading or paragraph block. Google's threshold for a good score is 2.5 seconds or less, measured at the 75th percentile of real visits.

Do Core Web Vitals actually affect rankings?+

They are a confirmed but modest signal, and Google has been consistent that relevance and content quality outrank page experience. The practical case for fixing LCP is conversion rather than position: slow pages lose visitors before the content is read, and that shows up in revenue faster than it shows up in rankings.

Why did my LCP get worse after I added lazy loading?+

Almost certainly because lazy loading was applied to every image including the one in the viewport. A lazy-loaded LCP image cannot begin downloading until the browser has done layout and run its intersection check, which adds hundreds of milliseconds of pure delay. The fix is to remove loading=lazy from above-the-fold images and add fetchpriority=high instead.

Is compressing the image enough to fix LCP?+

Often not. Compression reduces transfer time, but a large share of bad LCP scores are caused by discovery delay — the browser learning about the image late because it sits behind a CSS background rule, a JavaScript carousel, or a lazy-load attribute. A 40 KB image discovered 1.2 seconds late still fails. Fix discovery first, then size.

How do I find out which element is my LCP?+

PageSpeed Insights names it directly in the diagnostics section, and Chrome DevTools shows it in the Performance panel timeline marked as an LCP candidate. Field data in Search Console tells you which URL groups fail; lab tools tell you why on a specific page. You need both, because the lab environment often differs from what real visitors experience.

Do responsive images help LCP or hurt it?+

They help substantially when configured correctly — a phone downloading a 400 px-wide file instead of a 1600 px one is the single biggest win available on mobile. They hurt when the sizes attribute is wrong, because the browser then picks a candidate far larger than needed. An incorrect sizes value is one of the most common silent performance bugs on otherwise well-built sites.

Does image format choice matter for LCP?+

Yes, but less than discovery and dimensions. Moving a hero image from JPEG to AVIF might save 40 percent of its bytes, which is worth having. Moving it out of a JavaScript carousel so the browser finds it in the initial HTML can save far more wall-clock time. Do the structural work first, then the encoding work.

Let SEOpix handle the metadata

Filenames, alt text, EXIF fields and GPS coordinates written automatically as each image is generated. Start with 10 free images a month — no credit card required.

Keep reading