In short
Lazy-load every image below the initial viewport with native loading="lazy", but always load the hero and any above-the-fold image eagerly with explicit width/height set — lazy-loading the wrong image is one of the most common accidental Core Web Vitals regressions.
What lazy loading actually does
Native lazy loading defers an image's download until it's about to enter the viewport, instead of downloading every image on the page immediately regardless of whether the user ever scrolls to see it. On a long page with many images, this can cut initial page weight dramatically, since only the images actually visible on load are fetched right away.
The one-line implementation
``html <img src="photo.jpg" loading="lazy" width="800" height="600" alt="..."> ``
That's the entire native implementation — no library required in any modern browser. The width and height attributes matter as much as loading="lazy" itself: they let the browser reserve the correct space before the image loads, preventing a layout shift when it finally appears.
The mistake that undoes the benefit
Lazy-loading the hero image or anything visible without scrolling is the most common mistake, and it actively hurts performance rather than helping — it delays the largest visible element on the page, which directly worsens Largest Contentful Paint, one of the three Core Web Vitals. Above-the-fold images should load eagerly (loading="eager", or simply omitting the attribute, which defaults to eager).
Deciding the cutoff
A simple rule: anything visible in the initial viewport on a typical device loads eagerly; everything below it lazy-loads. If you're unsure where the fold falls across different devices, err toward eager loading for the first two or three images on the page and lazy for everything after — the downside of an unnecessary eager load is small; the downside of lazy-loading the hero is a real, measurable regression.
Preventing layout shift
Always set explicit width and height attributes (or use CSS aspect-ratio) on every image, lazy-loaded or not. Without this, the browser doesn't know how much space to reserve, so the layout jumps when the image finishes loading — a major contributor to Cumulative Layout Shift, which is directly penalized both as a user-experience problem and a ranking signal.
Background images and CSS
Native loading="lazy" only applies to <img> elements, not CSS background-image. For lazy-loading background images, use the Intersection Observer API to add the background only once an element scrolls near the viewport — slightly more code, but the same underlying principle.
Do you still need a JavaScript library?
For the vast majority of cases, no — native lazy loading is supported in all modern browsers and requires no dependency. A JavaScript-based lazy-load library is only worth the added weight for edge cases native loading doesn't cover, like the CSS background-image case above, or supporting genuinely old browsers that don't recognize the attribute.
A quick checklist
Hero and above-the-fold images: eager, with explicit dimensions. Everything below the fold: loading="lazy", with explicit dimensions. That covers the overwhelming majority of real-world cases correctly.
Related on Troiana: How to Optimize Images for Speed and Search.
Common questions
Do I need a JavaScript library for lazy loading?
No, in almost all modern cases — native loading="lazy" on the <img> tag is well-supported and requires no dependency. Reserve a library for edge cases like lazy-loading CSS background images.
Can lazy loading hurt performance?
Yes, if applied to above-the-fold images — this delays the largest visible content and directly worsens Largest Contentful Paint. Only lazy-load images below the initial viewport.
Why do I need width and height attributes on lazy-loaded images?
They let the browser reserve the correct space before the image downloads, preventing a layout shift (and a Cumulative Layout Shift penalty) when the image finally appears.
