Lazy Load Images Without Hurting LCP
16 Jan 2026Lazy loading every image on the page feels like a free performance win. It isn’t — lazy-load the wrong image and you delay your Largest Contentful Paint, which is the opposite of what you wanted.
The rule in one line
Lazy load everything below the fold. Never lazy load the image that’s visible when the page first loads — especially not your LCP candidate.
That’s the entire decision tree, and it’s the one thing people get backwards.
Why the hero image is the exception
loading="lazy" tells the browser to defer fetching the image until it’s about to enter the viewport. For an image already in the viewport on page load, that deferral serves no purpose — the browser has to fetch it anyway, right now — but the lazy attribute can still push it behind other work in the browser’s scheduling, adding delay to exactly the image most likely to be your LCP element.
// Wrong: this is above the fold
<img src="/hero.jpg" loading="lazy" alt="" />
// Right: explicit priority, no lazy loading
<img src="/hero.jpg" loading="eager" fetchpriority="high" alt="" />
With next/image, this is the priority prop, which also sets eager loading and adds a preload hint automatically:
<Image src="/hero.jpg" alt="" width={1200} height={600} priority />
Use priority on exactly one image per page — usually the one nearest the top. Marking several images as priority defeats the purpose; you’re back to loading everything eagerly, just with extra steps.
Below the fold: lazy load by default
Every image a user has to scroll to see should be lazy:
<img src="/product-3.jpg" loading="lazy" alt="" />
next/image does this automatically for every image without priority — you don’t need to add anything.
Plain <img loading="lazy"> has been supported in every major browser since 2020. You don’t need an IntersectionObserver library for this anymore; that was necessary before native lazy loading existed and is now extra JavaScript for a feature the browser gives you for free.
Always set width and height
This isn’t about loading speed — it’s about layout shift. Without explicit dimensions, the browser doesn’t know how much space to reserve, so the image loading in shoves the surrounding content down (that’s CLS). Set them even when the image is responsive:
<img src="/photo.jpg" width={800} height={600} style={{ width: '100%', height: 'auto' }} loading="lazy" alt="" />
The width/height attributes establish the aspect ratio for layout purposes; the CSS controls the actual rendered size. Both are needed.
Don’t lazy load images inside a carousel’s first slide
A common miss: a hero carousel where slide 1 is visible immediately but the component logic lazy-loads all slides uniformly, including slide 1. Check your carousel library’s defaults — most don’t special-case the first slide unless you tell them to.
Serve the right format and size, not just defer loading
Lazy loading controls when an image fetches, not how big it is. A 3000px source image served lazily is still a 3000px download once it comes into view. Pair lazy loading with responsive srcset or next/image’s automatic resizing so you’re not shipping desktop-sized images to a phone just because they arrived a bit later:
<img
srcset="/photo-400.jpg 400w, /photo-800.jpg 800w, /photo-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, 800px"
src="/photo-800.jpg"
loading="lazy"
alt=""
/>
The takeaway
Lazy load everything below the fold, using native loading="lazy" — no library needed. Never lazy load your LCP candidate; mark it priority/fetchpriority="high" instead. Set explicit dimensions on every image regardless of loading strategy, or you’ll trade an LCP problem for a CLS one.