TL;DR — Quick Summary
Lazy loading delays loading offscreen images and iframes until the user scrolls near them, reducing initial page weight by 50–80%. Use native `loading='lazy'` for images/iframes. Never lazy-load the LCP element — use `fetchpriority='high'` instead. Use Intersection Observer for custom lazy-loading of components and embeds.
What is Lazy Loading?
Lazy loading is a technique that defers the loading of non-critical resources until they are needed. The browser initially loads only above-the-fold content, then progressively loads additional resources as the user scrolls.
Native Lazy Loading (`loading='lazy'`): The simplest implementation. Add `loading='lazy'` to `<img>` and `<iframe>` elements. The browser handles all viewport detection and loading timing automatically. The browser uses an internal threshold (typically 1250–2500px below the viewport, varying by connection speed) to start loading before the element scrolls into view.
Intersection Observer API: A JavaScript API for detecting when elements enter the viewport. Provides more control than native lazy loading — custom thresholds, callbacks, and the ability to lazy-load any element type (not just images/iframes).
Facade Pattern: A specialized lazy loading technique for heavy third-party embeds. Replace the embed with a lightweight static placeholder (image + play button for YouTube, static map image for Google Maps) that loads the full embed only when the user clicks. This eliminates 200–800KB of JavaScript from initial page load.
Dynamic Import: JavaScript-level lazy loading using `import()` syntax. Loads JavaScript modules on-demand rather than upfront. Essential for code splitting in React (`React.lazy()`), Vue (async components), and other frameworks.
The key principle: load what's visible immediately, defer everything else.
Lazy Loading Thresholds
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| loading='lazy' support | All modern browsers | IE 11 (no support) | N/A |
| Intersection Observer | All modern browsers | IE 11 (polyfill needed) | N/A |
Google evaluates the 75th percentile (p75) of real-user field data over a rolling 28-day window.
History & Evolution
Key milestones:
- •2006 — Lazy loading concept popularized for images via JavaScript libraries.
- •2016 — Intersection Observer API introduced, replacing scroll event listeners for viewport detection.
- •2019 — Chrome 76 adds native `loading='lazy'` attribute — the first browser implementation.
- •2020 — Firefox and Safari add native lazy loading. All major browsers now support `loading='lazy'`.
- •2021 — WordPress 5.5 adds native lazy loading to all images automatically. This causes widespread LCP regressions because it lazy-loads hero images too.
- •2022 — WordPress 6.1 adds `fetchpriority='high'` and skips lazy loading on the first content image, fixing the LCP regression.
- •2023 — Chrome adjusts lazy-loading thresholds to be connection-speed-aware (starts loading earlier on fast connections, later on slow ones).
- •2025–2026 — Native lazy loading is the standard. loading='eager' (default) for above-fold, loading='lazy' for below-fold is the established pattern.
How Lazy Loading is Measured
Lazy loading effectiveness is measured by comparing initial page weight, request count, and CWV metrics with and without lazy loading:
Tools for measuring:
- •Chrome DevTools Network panel — Filter by 'Img' to see which images load initially vs on scroll. Check total transfer size at initial load vs after scrolling.
- •Lighthouse — 'Defer offscreen images' audit identifies images that should be lazy-loaded. 'Largest Contentful Paint image was lazily loaded' catches the LCP anti-pattern.
- •WebPageTest — Filmstrip view shows image loading timing. Waterfall shows which images load before and after user scroll simulation.
- •Chrome DevTools Performance panel — Shows image decode events and their impact on main thread work.
Key rule: Field data (CrUX) determines Google rankings. Lab data (Lighthouse, WebPageTest) is for debugging and iteration.
Common Causes of Poor Lazy Loading Scores
Common lazy loading issues that hurt performance:
- 1Lazy-loading the LCP image — The #1 lazy loading mistake. Adds 200–500ms to LCP. Never apply loading='lazy' to hero images, banner images, or any image that's the LCP element.
- 2Missing image dimensions — Lazy-loaded images without explicit width/height cause layout shifts (CLS) when they load and expand their container.
- 3Over-eager thresholds — Custom Intersection Observer implementations that wait until the image is fully in the viewport before loading create a visible 'pop-in' effect. Load 1000–2000px before the viewport.
- 4Not lazy-loading heavy embeds — YouTube iframes (800KB+ JS), Google Maps (300KB+ JS), and chat widgets load heavy JavaScript even when below the fold. Use facade patterns instead.
- 5Lazy-loading all images on WordPress — WordPress 5.5+ adds loading='lazy' to all images by default, including the first/LCP image. Must be overridden for above-fold images.
- 6No fallback for older browsers — While all modern browsers support native lazy loading, some Intersection Observer implementations lack fallbacks for progressive enhancement.
Frequently Asked Questions
For step-by-step optimization, platform-specific fixes, code examples, and case studies, read our full guide:
The Ultimate Guide to Core Web Vitals: How to Pass All Metrics & Boost Rankings in 2026Struggling with Lazy Loading?
Request a free speed audit and we'll identify exactly what's holding your scores back.