Skip to main content
Practical guide

Lazy Loading and SEO: Correct Implementation Guide 2026

Does lazy loading affect SEO?

Properly implemented lazy loading improves SEO by reducing initial page load time. However, applying lazy loading to the LCP element (above the fold) is a common mistake that harms the Largest Contentful Paint. The key rule: never apply lazy loading to images visible without scrolling, and use native loading='lazy' for everything else.

What is lazy loading and how it works in the browser

Lazy loading is the optimization that can backfire spectacularly if applied incorrectly. The technique consists of deferring the loading of non-visible resources until the user scrolls toward them, reducing the initial page weight and accelerating render time. In theory, it is one of the most effective web performance optimizations available. In practice, it is also one of the most frequently misimplemented, with direct consequences for the Largest Contentful Paint and, by extension, for Google rankings.

The principle is straightforward: if an image, an iframe, or a video is outside the user’s initial viewport, there is no reason to download it until the user approaches that area of the page. An article with 15 images where only 2 are visible without scrolling loads 13 unnecessary images if it does not implement lazy loading. Depending on the weight of those images, this can add between 2 and 10 MB of data transfer that the user does not need in the first second of loading.

The browser’s native mechanism, activated through the loading="lazy" attribute on <img> and <iframe> elements, works as follows: the browser detects the element’s position relative to the viewport and automatically decides when to initiate the download. Chrome, for example, uses a dynamic threshold that varies by connection type: on 4G connections, it begins loading images when they are approximately 1,250 pixels from the viewport; on slow connections, approximately 2,500 pixels. This intelligent behavior ensures the image is loaded before the user reaches it, eliminating the perception of a blank space.

Before native implementation, lazy loading required JavaScript libraries such as lazysizes, lozad.js, or IntersectionObserver polyfills. These solutions worked but added extra JavaScript to the page, creating an irony: a technique designed to reduce page weight ended up adding JavaScript weight to function. With the native loading="lazy" attribute, supported by 96% of global browsers according to Can I Use, the need for these libraries has disappeared for most use cases.

The relationship between lazy loading and SEO is direct. Google uses the Largest Contentful Paint as one of the three Core Web Vitals metrics that affect rankings. LCP measures how long it takes for the largest visual element in the initial viewport to fully load. If that element is an image with lazy loading, the browser delays its download because it first needs to render the page layout to determine whether the image is in the viewport, and only then initiates the download. This additional delay can add between 1 and 3 seconds to the LCP, a devastating penalty in terms of performance and ranking potential.

For a comprehensive view of how web speed affects SEO and business outcomes, see our guide on web speed and SEO.

Native lazy loading with loading=“lazy”: advantages and limitations

The HTML loading="lazy" attribute represented a fundamental shift in web image optimization when Chrome implemented it in version 76 in 2019. For the first time, developers could defer image loading without JavaScript dependencies, without complex configuration, and without risking broken indexing in Google.

The implementation is deliberately simple: <img src="photo.jpg" loading="lazy" alt="Description" width="800" height="600">. The width and height attributes are essential in this context, not optional. Without explicit dimensions, the browser cannot calculate the element’s position relative to the viewport without first downloading it, which partially negates the lazy loading benefit and can cause CLS (layout shifts) when the image finally loads and pushes surrounding content.

The advantages of the native attribute are significant. First: zero JavaScript. The browser manages the entire viewport detection and loading process, which means no impact on the JavaScript bundle weight or parse time. Second: Googlebot compatibility. Google’s official documentation confirms that the loading="lazy" attribute works correctly with its crawler, which renders pages as a full Chrome browser. Third: graceful degradation. Browsers that do not support the attribute simply ignore it and load the image normally, breaking nothing.

However, native lazy loading has limitations worth understanding. The primary one is that the developer has no control over the loading threshold: Chrome decides when to start the download based on its own algorithms. This means in some cases, the browser may be too conservative (loading images too early, reducing the benefit) or too aggressive (loading too late, briefly showing a blank space). In practice, Chrome’s thresholds are well calibrated for most situations, but there are scenarios where finer control is necessary.

Another limitation is the absence of transition effects. With native loading="lazy", the image appears abruptly when it loads. There is no fade-in, blur-up, or any visual effect to smooth the appearance. For sites where visual experience is a priority — photography portfolios, fashion e-commerce, design websites — this limitation may justify using complementary JavaScript solutions.

The correct usage pattern on a typical page is as follows: above-the-fold images (hero, logo, first sections) use loading="eager" (the default value, which can be omitted) and the LCP image additionally receives fetchpriority="high" to tell the browser to prioritize it over other resources. All below-the-fold images use loading="lazy". This combination maximizes the speed of the first visual impression without penalizing the LCP.

Intersection Observer API: advanced lazy loading with JavaScript

The Intersection Observer API is the modern JavaScript mechanism for implementing lazy loading with granular control. Unlike the native attribute, which delegates all logic to the browser, Intersection Observer allows the developer to define exactly when, how, and under what conditions deferred resources load.

The operation of Intersection Observer is elegant: an observer is created that monitors whether an element enters the viewport (or a defined area around the viewport) and executes a callback when that occurs. The browser internally optimizes this detection, batching intersection calculations and executing them asynchronously, which makes it significantly more efficient than older solutions based on scroll listeners.

A typical implementation pattern starts by creating the observer with a rootMargin of 200px 0px (this loads images when they are 200 pixels from the viewport, not when they are already visible) and a threshold of 0.01 (it triggers when barely 1% of the element is visible). The callback receives intersecting entries, replaces the data-src attribute with src to initiate loading, and stops observing the element once loaded.

The primary advantage of Intersection Observer over native lazy loading is control over the rootMargin. On slow connections, a rootMargin of 300-500 pixels gives the browser more time to download the image before it becomes visible. On fast connections, a rootMargin of 100-200 pixels reduces unnecessary downloads. This adaptation to the network context is something the native attribute does internally, but that the developer cannot configure.

Another use case where Intersection Observer surpasses native is lazy loading of content that is not images: React components that only render when visible, sections of a single-page application that load data on demand, or analytics modules that initialize only when the user reaches a specific page section. The loading="lazy" attribute only works with <img> and <iframe>, while Intersection Observer works with any DOM element.

From an SEO perspective, Intersection Observer requires an additional precaution that native lazy loading does not. Googlebot executes JavaScript, but its rendering process has particularities: it renders the page in a single pass without simulating scroll. This means that if your Intersection Observer implementation depends on actual scrolling to trigger, Googlebot may not see the images. The solution is to use the src attribute with a low-resolution placeholder image and data-src with the actual image, ensuring Googlebot at least has access to the placeholder. Alternatively, including a <noscript> tag with the original image as a fallback guarantees indexing even if JavaScript fails.

The practical recommendation for most sites in 2026 is clear: use native loading="lazy" as the default solution and reserve Intersection Observer only for cases requiring advanced control over loading behavior. The simplicity and reliability of the native attribute outweigh the advantages of manual control in 90% of scenarios.

The most critical mistake: lazy loading on the LCP element

If there is one single concept to retain from this guide, it is this: never, under any circumstances, apply lazy loading to the image that constitutes the Largest Contentful Paint of your page. This mistake, documented by Google as one of the most frequent in Core Web Vitals audits, directly penalizes your LCP and, by extension, your organic rankings.

The mechanism by which lazy loading harms LCP is technical but comprehensible. When the browser encounters an image with loading="lazy", it follows this process: first it parses the HTML, builds the DOM and CSSOM, calculates the page layout, determines which elements are in the viewport, and only then initiates the download of lazy-marked images that turn out to be visible. This process adds a delay that does not exist with a normally loaded image (loading="eager"), where the browser begins downloading the image as soon as it encounters the tag in the HTML, in parallel with the rest of the parsing.

Chrome User Experience Report data shows that applying lazy loading to the LCP element increases LCP time by between 1,200 and 3,000 milliseconds on average, depending on connection speed. On a page with an LCP of 1.8 seconds (within Google’s “good” threshold), adding lazy loading can push it to 3-4.8 seconds, crossing directly into the “poor” threshold that Google penalizes in rankings.

The problem is particularly insidious because many CMSs and frameworks apply lazy loading automatically to all images. WordPress, since version 5.5, adds loading="lazy" to all <img> tags by default. Some themes and plugins override this logic to exclude the header image, but many do not. The result: millions of WordPress sites have their LCP image with lazy loading without the owner’s knowledge.

Auditing this issue is straightforward. PageSpeed Insights explicitly flags when it detects lazy loading on the LCP element with the message “Don’t lazy-load Largest Contentful Paint image.” Lighthouse Core Web Vitals also flags it. But the most reliable way to detect it is to inspect the page source code and verify that the hero image or the main visible-without-scrolling image does not have the loading="lazy" attribute.

The correct pattern for the LCP image includes three elements: loading="eager" (or simply omitting the loading attribute, which has the same effect), fetchpriority="high" to indicate to the browser that it should prioritize it over other resources, and a preload in the document <head>: <link rel="preload" as="image" href="hero-image.webp" fetchpriority="high">. This combination ensures the browser begins downloading the LCP image as early as possible, even before fully constructing the page layout.

Core Web Vitals depend directly on decisions like this. An optimized LCP is not just a technical metric: it is a direct ranking factor that affects your page’s organic visibility.

Lazy loading for iframes, videos, and embedded content

The impact of lazy loading on iframes is proportionally greater than on images because a YouTube iframe, Google Maps embed, or social widget loads its own entire HTML, CSS, JavaScript, and associated resources. A single YouTube iframe without lazy loading can add between 500 KB and 1.5 MB of data transfer and several additional HTTP connections, all for an element that may not be visible without scrolling.

The loading="lazy" attribute works natively with <iframe> in the same browsers that support it for <img>. The implementation is identical: <iframe src="https://www.youtube.com/embed/ID" loading="lazy"></iframe>. However, there is an important difference: while an image with lazy loading simply does not download until needed, an iframe with lazy loading downloads nothing from the embedded domain until the iframe enters the viewport. This means that if the iframe contains JavaScript that registers events or sets cookies, those actions are deferred as well.

For embedded videos, the best practice in 2026 combines lazy loading with the “facade” technique: instead of loading the YouTube iframe directly, a thumbnail image of the video is displayed with a play button. Only when the user clicks play is the image replaced with the actual YouTube iframe. This technique, recommended by web.dev in their performance guide, completely eliminates the iframe load for users who never interact with the video (which, according to Wistia studies, is between 60% and 80% of visitors to a page with an embedded video).

Third-party widgets — chatbots, subscription forms, booking calendars, podcast players — represent a special case. Many of these widgets load through scripts that dynamically inject iframes, and the loading="lazy" attribute does not work with JavaScript-created iframes. For these cases, the solution is to use Intersection Observer to detect when the widget container is near the viewport and only then inject the widget script.

Google Maps is a paradigmatic case of the benefit of iframe lazy loading. A Google Maps embed without lazy loading initiates between 15 and 25 HTTP connections and downloads approximately 800 KB of data. On a contact page where the map is below a form and a text block, most users see the map only after scrolling. Applying loading="lazy" to the map iframe eliminates that initial 800 KB load, significantly improving the total page load time.

From an SEO perspective, iframes with lazy loading do not negatively affect the indexing of the main page content. Google does not index content inside iframes as part of the containing page, so deferring their load has no impact on what content Googlebot sees. What it does improve is the page’s performance score, which indirectly benefits rankings.

How to verify that your lazy loading works correctly

Implementing lazy loading is only half the work. Verifying that it functions correctly — and that it is not causing unintended problems — is the other half, and the one most developers skip.

The first check is visual: open your page in Chrome DevTools with the Network tab open, filter by “Img,” and scroll slowly downward. You should see image requests appear progressively as you approach each image, not all at once when the page loads. If all images load immediately, lazy loading is not working. If no image loads until you literally see it, the threshold is too aggressive and users will see blank spaces.

The second check is technical: ensure the LCP image does not have loading="lazy". In DevTools, inspect the LCP element (you can identify it with Lighthouse, which marks it specifically) and confirm it either has no loading attribute or has loading="eager". If you use a CMS that adds lazy loading automatically, verify that the first image in each page template is excluded from the automatic logic.

The third check is for indexing. In Google Search Console, use the URL inspection tool to render a page with lazy loading and verify that images appear in the rendered HTML. You can also use Google’s Rich Results Test to see how Googlebot sees your page. If images do not appear in the rendered view, your JavaScript lazy loading implementation has a problem that the native attribute would not have.

The fourth check is for performance. Run Lighthouse on your main page templates (home, listing, detail) and review the specific lazy loading audits: “Defer offscreen images” (should be green if lazy loading works) and “Don’t lazy-load LCP image” (should not appear). WebPageTest offers a more detailed analysis with its filmstrip view that shows frame by frame how the page loads, allowing you to detect if there are images that should be visible but appear delayed.

For sites with multiple templates (e-commerce with product pages, category pages, home, blog), the verification must be repeated for each template type, because the LCP image varies among them. On the home page it may be the hero banner; on a product page, the first product photo; on a blog post, the featured image. Each of these images must be excluded from lazy loading.

A recommended practice is to automate these checks with tools like pa11y or unlighthouse, which allow auditing hundreds of URLs in batch and generating performance reports that include lazy loading metrics. Integrating these audits into the CI/CD pipeline ensures that future site updates do not accidentally reintroduce lazy loading on the LCP element.

The difference between correctly implemented lazy loading and default-applied lazy loading can be the difference between a 1.5-second LCP and a 4-second LCP. In a market where Google uses Core Web Vitals as a ranking factor, that difference has a direct impact on organic visibility and, ultimately, on business revenue.

Key takeaways

  • Applying lazy loading to the LCP image (above the fold) is the most critical mistake: it can increase LCP by 1 to 3 seconds
  • The native loading='lazy' attribute has 96% browser support and requires no JavaScript, making it the most reliable solution
  • Google can crawl and index images with native lazy loading without issues, but JavaScript-based lazy loading requires additional precautions
  • The correct pattern is: eager load for above-the-fold images, loading='lazy' for everything else, and fetchpriority='high' for the LCP element
  • Intersection Observer API provides granular control over when to load resources, with a recommended rootMargin of 200-300px for pre-scroll loading

Comparison: lazy loading SEO

Feature lazy loading SEOAlternative
Can Google crawl images with lazy loading? Google can crawl and index images using native lazy loading (the loading='lazy' attribute) without issues, as Googlebot executes JavaScript and processes the full page. However, custom JavaScript implementations that rely on scroll events may fail if the content never becomes visible during Googlebot's rendering pass. Google's official documentation recommends the native loading='lazy' attribute as the preferred method.-
Should I use lazy loading on all images? No. Images visible without scrolling (above the fold) should never have lazy loading. This includes the hero image, the logo, any image that is the LCP element of the page, and images in the first visible sections. Applying lazy loading to these images delays their loading because the browser first needs to identify that they are in the viewport before starting the download, adding unnecessary latency to the LCP.-
Is native lazy loading sufficient or do I need a library? For most cases, the native loading='lazy' attribute is sufficient and preferable. Its main advantage is that it works without JavaScript, meaning zero performance impact and full Googlebot compatibility. JavaScript libraries are only necessary if you need advanced control such as transition effects, bandwidth-based loading, or support for very old browsers that already represent less than 4% of the market.-

Frequently asked questions

Can Google crawl images with lazy loading?

Google can crawl and index images using native lazy loading (the loading='lazy' attribute) without issues, as Googlebot executes JavaScript and processes the full page. However, custom JavaScript implementations that rely on scroll events may fail if the content never becomes visible during Googlebot's rendering pass. Google's official documentation recommends the native loading='lazy' attribute as the preferred method.

Should I use lazy loading on all images?

No. Images visible without scrolling (above the fold) should never have lazy loading. This includes the hero image, the logo, any image that is the LCP element of the page, and images in the first visible sections. Applying lazy loading to these images delays their loading because the browser first needs to identify that they are in the viewport before starting the download, adding unnecessary latency to the LCP.

Is native lazy loading sufficient or do I need a library?

For most cases, the native loading='lazy' attribute is sufficient and preferable. Its main advantage is that it works without JavaScript, meaning zero performance impact and full Googlebot compatibility. JavaScript libraries are only necessary if you need advanced control such as transition effects, bandwidth-based loading, or support for very old browsers that already represent less than 4% of the market.

Sources and references

  1. Intersection Observer API (developer.mozilla.org)
  2. Chrome: Loading Attribute (developer.chrome.com)