Lazy loading is one of those performance optimizations where the implementation details matter as much as the technique itself. Done right, it improves page load times, reduces bandwidth consumption, and has no negative SEO impact. Done wrong — specifically, applied to above-the-fold content or your LCP image — it actively hurts your Core Web Vitals scores and page experience ranking signals. This guide covers exactly where to draw the line.
What Lazy Loading Does (and Doesn’t Do)
Lazy loading defers the loading of non-critical resources — images, iframes, videos — until they’re needed. Instead of loading all images on a page at once during initial page load, lazy loading loads images as the user scrolls toward them. The result:
- Faster initial page load (fewer bytes downloaded on first load)
- Reduced bandwidth consumption (users who don’t scroll down don’t download images they never see)
- Faster Time to First Byte and First Contentful Paint in many cases
What lazy loading does NOT do: it doesn’t affect the LCP of above-the-fold content (or it shouldn’t — applied incorrectly, it does the opposite), it doesn’t reduce total page weight for users who scroll through everything, and it doesn’t make images load faster when they do trigger — only faster relative to when the page first loads.
The Fundamental Rule: Above the Fold vs. Below the Fold
This is the decision that determines whether lazy loading helps or hurts your SEO:
| Content Position | Loading Strategy | Reason |
|---|---|---|
| Above the fold (visible on page load) | Eager loading (default) or fetchpriority=high | Must render immediately for good LCP and user experience |
| LCP element specifically | fetchpriority=”high” + preload in <head> | LCP directly affects Core Web Vitals ranking signal |
| Below the fold (requires scrolling) | loading=”lazy” | Safe to defer — not needed for initial render |
| Iframes above fold | Facade or eager | YouTube/map iframes above fold should use facade pattern |
| Iframes below fold | loading=”lazy” | Defer iframe load until proximity to viewport |
| CSS background images | JavaScript-based lazy load or IntersectionObserver | Native lazy loading only works on HTML img and iframe elements |
Native Lazy Loading: The Modern Standard
Implementation
Native lazy loading uses a single HTML attribute — no JavaScript required:
<!-- Lazy load a below-the-fold image -->
<img src="product-photo.webp"
alt="Blue running shoes - men's size 10"
width="800"
height="600"
loading="lazy">
<!-- Lazy load an iframe (YouTube, map, social embed) -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"
width="560"
height="315"
loading="lazy"
title="Video title">
</iframe>
Browser Support
Native lazy loading (loading=”lazy”) is supported by:
- Chrome 76+ (2019)
- Firefox 75+ (2020)
- Safari 15.4+ (2022)
- Edge 79+ (2020)
Global browser support is approximately 94% as of 2026. Browsers that don’t support loading=”lazy” simply ignore the attribute and load the resource normally — so adding it is safe and backward-compatible.
Distance-From-Viewport Threshold
Browsers don’t wait until the image is literally in the viewport before starting to load it. They use a distance threshold — loading begins when the image is within a certain distance of the visible viewport:
- Fast connections (4G+): ~1,250px below the visible viewport
- Slow connections (3G): ~2,500px below the visible viewport
These thresholds help ensure images load smoothly before the user reaches them during scroll. The thresholds vary slightly by browser implementation.
LCP and Lazy Loading: The Critical Interaction
Never Lazy Load the LCP Image
The LCP (Largest Contentful Paint) is the largest visible element on the page at load time — typically your hero image, featured image, or largest above-the-fold content block. It’s one of the three Core Web Vitals metrics, measured as seconds from navigation start to render of the LCP element.
Applying loading=”lazy” to the LCP image tells the browser to defer loading it — which directly increases LCP time. In many cases, this is the single highest-impact CWV mistake on a page. A hero image with loading=”lazy” can add 1–3 seconds to LCP, moving from “Good” to “Poor” threshold in one attribute.
Identifying Your LCP Element
Use Chrome DevTools Performance panel or the LCP element indicator:
- Open Chrome DevTools → Performance tab
- Record a page load
- Look for the LCP marker in the timeline — it will identify the specific DOM element
- Alternatively: Chrome DevTools → Elements → right-click on suspected LCP element → “Reveal in Performance Panel”
- Or: PageSpeed Insights/Lighthouse will identify the LCP element in the diagnostics section
Optimizing the LCP Image
<!-- In <head> - preload the LCP image -->
<link rel="preload"
as="image"
href="/images/hero-banner.webp"
fetchpriority="high">
<!-- In body - the LCP image itself -->
<img src="/images/hero-banner.webp"
alt="Over The Top SEO - Enterprise SEO Agency"
width="1200"
height="600"
fetchpriority="high"
loading="eager">
<!-- Note: loading="eager" is the default; explicitly setting it
prevents CMS systems from auto-adding lazy loading to all images -->
Googlebot and Lazy Loading: What Actually Gets Indexed
Googlebot’s JavaScript Rendering
Googlebot uses a Chromium-based renderer and executes JavaScript. It can see and index content that’s lazy-loaded via JavaScript (Intersection Observer API, lazysizes library, custom implementations). The key distinction from native lazy loading:
- Native lazy loading (loading=”lazy”): Googlebot loads images within the distance-from-viewport threshold — practically, images within 1,250px+ of the viewport top are likely loaded and indexed
- JavaScript-based lazy loading: Googlebot renders JavaScript but doesn’t scroll — content that only loads after scrolling may not be triggered by Googlebot’s renderer
Verifying Image Indexation
To check if Googlebot is indexing your lazy-loaded images:
- Google Search Console → URL Inspection → enter a page URL → “Test Live URL”
- View “Rendered HTML” — search for your lazy-loaded image src paths
- If your images appear in the rendered HTML, Googlebot can see them
- If they don’t appear, your lazy loading implementation may be blocking Googlebot’s access
Critical Images Below the Fold
For content-heavy pages where below-the-fold images are important for ranking (product images, diagram images in content, infographics), verify Googlebot is loading them. If URL Inspection shows they’re missing, consider using native lazy loading (which has better Googlebot compatibility) instead of custom JavaScript implementations.
JavaScript Lazy Loading Approaches
When native lazy loading isn’t sufficient (CSS background images, custom frameworks, complex loading scenarios), JavaScript implementations are the alternative:
Intersection Observer API
// Modern, performance-friendly lazy loading
const lazyImages = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
}, {
rootMargin: '0px 0px 500px 0px' // Start loading 500px before viewport
});
lazyImages.forEach(img => imageObserver.observe(img));
HTML for Intersection Observer lazy loading:
<img data-src="/images/product.webp"
src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
alt="Product description"
width="800"
height="600">
lazysizes Library
lazysizes (aFarkas/lazysizes) is a popular JavaScript lazy loading library with additional features over native loading:
- Support for responsive images (srcset) lazy loading
- CSS background image lazy loading
- LQIP (Low Quality Image Placeholder) support
- Automatic LCP protection (won’t lazy load elements with immediate visibility)
<!-- With lazysizes -->
<img data-src="product.webp"
data-srcset="product-400.webp 400w, product-800.webp 800w"
class="lazyload"
alt="Product"
width="800" height="600">
<!-- CSS background image -->
<div class="lazyload" data-bg="url('background.webp')"></div>
YouTube and Video Embed Optimization
The YouTube Performance Problem
A single YouTube iframe loads approximately 400–500KB of JavaScript, multiple additional network connections, and significantly increases page load time. For pages with multiple embeds, this is a substantial performance burden.
Facade Pattern (Recommended)
Replace the iframe with a static thumbnail that only loads the full iframe when clicked:
<!-- Lite YouTube Embed (open source) -->
<lite-youtube videoid="VIDEO_ID"
playlabel="Watch: Video Title">
</lite-youtube>
<!-- Manual facade pattern -->
<div class="youtube-facade"
data-videoid="VIDEO_ID"
style="background-image:url('https://img.youtube.com/vi/VIDEO_ID/maxresdefault.jpg')">
<button aria-label="Play video">▶</button>
</div>
<script>
document.querySelectorAll('.youtube-facade').forEach(el => {
el.addEventListener('click', () => {
el.innerHTML = `<iframe src="https://www.youtube.com/embed/${el.dataset.videoid}?autoplay=1"
loading="lazy" allowfullscreen></iframe>`;
});
});
</script>
Impact
Switching from eager YouTube iframe to facade pattern typically saves 400–600KB on page load and can improve LCP by 1–2 seconds on pages where the embed was loading before the LCP image.
Responsive Images and Lazy Loading
Lazy loading works with responsive image syntax — use both for maximum optimization:
<img srcset="image-400.webp 400w,
image-800.webp 800w,
image-1200.webp 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1000px) 800px,
1200px"
src="image-800.webp"
alt="Descriptive alt text"
width="1200"
height="800"
loading="lazy">
The browser uses srcset + sizes to download the appropriately sized image for the user’s device — lazy loading ensures it only downloads when needed. Together, these optimizations can dramatically reduce data transfer for image-heavy pages.
CMS-Specific Implementation Notes
WordPress
WordPress added native lazy loading to the_post_thumbnail and wp_get_attachment_image since version 5.5 (2020). Images rendered via these functions automatically include loading=”lazy”. Critical issue: it applies to ALL images, including featured images. If your theme displays the featured image above the fold as the LCP element, it will have loading=”lazy” applied — which hurts LCP.
Fix for WordPress:
// Remove lazy loading from the featured image (LCP element)
add_filter('wp_lazy_loading_enabled', function($default, $tag_name, $context) {
if ($context === 'the_post_thumbnail' && is_singular()) {
return false;
}
return $default;
}, 10, 3);
Shopify
Shopify themes handle lazy loading at the theme level — check your theme’s image component for loading attribute handling. Product images above the fold (main product gallery) should not be lazy loaded; below-fold recommendation images and collection page images further down the page should be.
Lazy Loading Audit Checklist
- ☐ LCP image identified — does it have loading=”lazy”? If yes, remove it immediately.
- ☐ All above-the-fold images checked — none should have loading=”lazy”
- ☐ Below-the-fold images have loading=”lazy” attribute
- ☐ Iframes (YouTube, maps) use loading=”lazy” or facade pattern
- ☐ Preload tag in <head> for LCP image
- ☐ fetchpriority=”high” on LCP image
- ☐ Verify lazy-loaded images appear in Google Search Console URL Inspection rendered HTML
- ☐ Run PageSpeed Insights — check for “Defer offscreen images” recommendation (validates lazy loading working) vs. “Largest Contentful Paint image was lazily loaded” warning (problem)
- ☐ Width and height attributes on all images (prevents CLS from images that resize on load)
Lazy loading mistakes — especially on LCP images — are some of the most impactful CWV fixes available. We audit and fix technical performance issues that directly affect your Google rankings.
Frequently Asked Questions
Does lazy loading hurt SEO?
Lazy loading does not inherently hurt SEO — Googlebot can render JavaScript-loaded content. However, lazy loading applied to above-the-fold images (especially your LCP element) will hurt Core Web Vitals by delaying the largest contentful paint. The key rule: never lazy load above-the-fold content.
What is the difference between native lazy loading and JavaScript lazy loading?
Native lazy loading uses the HTML loading=’lazy’ attribute — no JavaScript required, supported by all modern browsers. JavaScript lazy loading uses the Intersection Observer API or libraries like lazysizes. For new implementations, native lazy loading is preferred. JavaScript lazy loading is still needed for CSS background images and complex framework scenarios.
Can Googlebot index lazy-loaded images?
Yes, Googlebot can index lazy-loaded images. Googlebot renders JavaScript and loads images within the distance-from-viewport threshold. For critical images, verify they appear in Google Search Console URL Inspection’s rendered HTML to confirm Googlebot is loading them.
Should I lazy load the LCP image?
Never lazy load the LCP image. Doing so directly increases your LCP time and hurts Core Web Vitals. Instead, use fetchpriority=”high” on your LCP image and preload it in the <head>. This is one of the highest-impact CWV fixes you can make if your LCP image is currently being lazy loaded.
What resources should be lazy loaded?
Lazy load: all below-the-fold images, embedded iframes (YouTube videos, maps), non-critical scripts, below-the-fold CSS background images. Do NOT lazy load: the LCP image, any above-the-fold images, page header/logo images, or any images visible on initial load without scrolling.
How do I lazy load YouTube embeds without hurting performance?
Use a facade — replace the iframe with a static thumbnail image and play button that only loads the actual YouTube iframe when clicked. Lite YouTube Embed (paul-irish/lite-youtube-embed) is a popular open-source solution that avoids loading YouTube’s 400-500KB script bundle on page load.