Lazy Loading

How lazy loading images and iframes improves Core Web Vitals, the loading attribute, the SEO risks of lazy-loading above-the-fold content, and how Googlebot renders deferred content.

First published: Jul 2, 2026 · Last updated: Jul 26, 2026 · Advanced
demand #7 in Web Performance#51 in Technical SEO#73 on the site

Lazy loading defers off-screen images and iframes until they're about to scroll into view, cutting initial page weight and helping Core Web Vitals. The native way is the loading="lazy" attribute on <img> and <iframe> — no JavaScript needed. The big mistake is lazy-loading your hero/LCP image, which delays Largest Contentful Paint. Googlebot doesn't scroll or click, so anything gated behind a scroll or click event can go unseen. It's not a direct ranking factor — the effect runs through Core Web Vitals and crawlability. Verify what actually renders in Search Console's URL Inspection Tool: the image URLs should sit in the src attribute of the rendered HTML.

TL;DR — Lazy loadingLazy loading defers loading of off-screen or non-critical resources — usually images and iframes — until they're about to enter the viewport. The native way to do it is the loading=\"lazy\" HTML attribute, which needs no JavaScript. defers off-screen images and iframesHTML element that displays one webpage inside another — how embeds work. until they near the viewport, cutting initial page weight (helps LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good.) and startup main-thread work (helps INPInteraction to Next Paint — the input-to-paint latency at the 75th percentile of a page's interactions. ≤200 ms is good.). The native loading="lazy" attribute on <img>/<iframe> has replaced most JS libraries; only lazy and eager are meaningful values (auto is deprecated). The damaging mistakes: lazy-loading the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good./above-the-fold image (delays the very metric you’re chasing), and gating content behind scroll/click — which GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. never triggers because it doesn’t interact with the page. It’s not a direct ranking factor; the effect runs through Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. and crawlabilityCrawlability is how well search engine crawlers can discover, access, and fetch a site's pages. A crawlability issue is any technical condition — blocked access, broken links, server failures, or bloated URL inventory — that stops pages from reaching the index.. Verify in Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results.’s URL Inspection ToolA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. that image URLs land in the src attribute of the rendered HTML.

What lazy loading actually does

The idea is simple: only load resources when you need them, instead of loading everything at once. On a media-heavy page, downloading every image and embed up front keeps the browser busy fetching things the visitor may never scroll to — burning bandwidth, memory, and battery for nothing. Deferring the off-screen ones lets the above-the-fold content paint sooner. Martin Splitt made exactly this point on Google’s Search Off the Record episode “Lazy loading demystified”: the goal is to avoid work that yields nothing, because non-critical images the page would be fine without just keep the browser occupied.

This connects to the Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. most people are chasing. Fewer bytes competing for the network up front means the Largest Contentful Paint element can render sooner. For iframes — ads, social widgets, comment sections, maps — deferring them also cuts main-thread work during startup, which is an Interaction to Next Paint win, not just an LCP one. Google’s own web.dev guidance frames lazy-loaded iframes as an INPInteraction to Next Paint — the input-to-paint latency at the 75th percentile of a page's interactions. ≤200 ms is good. improvement during page load.

Native vs. JavaScript-driven lazy loading

A few years ago, browsers gained a native loading attribute for images and iframes, so you can hand the whole job to the browser instead of wiring up a JavaScript API. In my JavaScript SEO guide at Ahrefs I make the same observation: since I first wrote that piece, lazy loading has mostly moved from being JavaScript-driven to being handled by browsers. You’ll still run into JS-driven setups, and for images they’re usually fine — the thing I check is whether actual content (not just images) is being lazy loaded, because those setups are the ones that have caused content not to be picked up correctly.

The native version:

<!-- Below-the-fold image: defer it -->
<img src="gallery-07.jpg" loading="lazy" width="800" height="600" alt="…">

<!-- Off-screen embed: defer it -->
<iframe src="https://www.youtube.com/embed/…" loading="lazy" title="…"></iframe>

Always set explicit width/height (or an aspect-ratio) on lazy images so the browser reserves the space before the image loads — that’s the biggest layout-shift risk with deferred images. Reserved dimensions aren’t an absolute CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good. guarantee, though: if the surrounding layout or a responsive crop still changes after the image loads, you can still see a shift, so confirm with a real layout-shift trace rather than assuming fixed dimensions alone settle it.

Lazy loading and iframes need one more distinction: loading="lazy" on an <iframe> only defers when the embed’s fetch and creation happen. It doesn’t set its title, focus behavior, sandbox, allow/permissions policy, referrerpolicy, consent handling, or dimensions for you — those still need their own attention, and an embed can keep doing work (scripts, tracking pixels, layout) once it becomes eligible to load. And don’t assume “below the fold” behaves the same everywhere: display: none content, offscreen carousel slides, transformed elements, and nested scroll containers can intersect the viewport differently than a plain below-the-fold element, so test the actual layout and navigation controls rather than assuming equivalence.

The loading attribute’s values

Only two values matter today:

  • loading="lazy" — defer the resource until it’s near the viewport.
  • loading="eager" — load it immediately, the default behavior. Use it to be explicit about above-the-fold images.
Evidence for this claim The native loading attribute supports lazy loading for images and iframes without a JavaScript lazy-loading library. Scope: Browser-level lazy loading behavior; browser heuristics decide the fetch distance. Confidence: high · Verified: web.dev: Browser-level image lazy loading

You may see loading="auto" in older articles — it’s deprecated in Chrome, so don’t reach for it. There’s no need to; omitting the attribute already gives you the default (eager) behavior.

How near is “near”? loading="lazy" is a hint, not an author-controlled guarantee — the spec leaves the actual near-viewport decision to the browser. Chromium tries to fetch a lazy resource early enough that it’s ready by the time you scroll to it, and the trigger distance varies by browser, connection speed, and resource type; it isn’t a fixed pixel value you can rely on or reproduce across browsers or versions. Don’t publish — or trust — a specific “loads N pixels before the viewport” number; treat the near-viewport window as implementation-defined and confirm actual behavior with a Network trace on the browser/connection you care about instead of assuming a constant.

Native lazy loading also plays fine with responsive images: it applies to ordinary src and srcset/sizes selection, so you don’t lose responsive image behavior by adding loading="lazy". If you instead hide the real URL only in a data-* attribute for a script to swap in later, loading now depends on that script — test the rendered HTML and what happens if the script fails (more on this in the Scripts and Troubleshooting tabs).

The #1 mistake: lazy-loading the LCP / above-the-fold image

This is the failure I see most, and every source agrees on it. If you lazy-load the hero image — or any image likely to be the LCP element — you’ve told the browser to wait on the single most important pixel for perceived load speed. The browser also can’t lazy-load an image until it knows where the image will sit on the page, so lazy above-the-fold images tend to load more slowly than eager ones. That directly delays Largest Contentful Paint, the metric Google says should resolve within the first 2.5 seconds of load.

Do not lazy-load the observed or likely LCP image; confirm the actual request and paint timing in a trace. Source: Lazy Loading

The eager path discovers the likely LCP image in HTML and starts its request promptly. The lazy path waits for browser loading heuristics before request start. The comparison uses no fixed timing values and notes that actual LCP must be measured.

© Patrick Stox LLC · CC BY 4.0 ·

Splitt put the flip side plainly on the SOTR episode: if you’re not using lazy loading where you should, that will probably hurt some aspect of Core Web VitalsWeb Vitals is Google's initiative (launched May 2020) for unified page-experience quality signals. Core Web Vitals — LCP, INP, and CLS — are the subset used in ranking; the rest (TTFB, FCP, TBT, Speed Index) are diagnostic, not ranking factors. — most likely LCP. So it cuts both ways. The rule:

  • Probable or observed LCP candidate → load eagerly (omit loading="lazy", or set eager). Consider fetchpriority="high" on the LCP image.
  • Below the fold → loading="lazy".

Not every image above the fold is the LCP candidate — identify the actual one (a Performance trace or PageSpeed InsightsPageSpeed Insights (PSI) is a free Google tool at pagespeed.web.dev that reports two kinds of data for a URL: real-user field data from the Chrome UX Report and a single Lighthouse lab run with the 0–100 Performance score. Only the field Core Web Vitals are what Google uses for ranking. will name it) and verify its request timing rather than treating every first-screen image the same. And these are separate hints, not one setting: loading="eager" (or omitting loading) only means the browser won’t defer discovery of the resource — it doesn’t itself raise fetch priority. fetchpriority is a distinct, advisory hint on top of that. Stacking a <link rel="preload"> with loading="lazy" on the same resource sends the browser conflicting intent, so check the actual network waterfall rather than assuming the combination does what you expect.

The blanket anti-pattern is switching on lazy loading for every image site-wide — a common CMSA content management system (CMS) is software that lets users create, manage, and publish digital content — like blog posts and pages — without writing raw code. WordPress, Drupal, and Joomla are the most common open-source CMS platforms. default. As Splitt noted, if every image is lazy loaded, then images that are (or should be) immediately visible get lazy loaded too, which is exactly the case you want to avoid.

How Googlebot renders lazy-loaded content

Here’s the crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. reality that trips people up: GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. doesn’t scroll, and it doesn’t click. It renders your page with a headless browser, but it doesn’t simulate a user interacting with it. Google states this directly — its recommended lazy-loading methods deliberately don’t rely on user actions like scrolling or clicking, because Google Search does not interact with your page.

Evidence for this claim Google Search does not scroll or click to trigger lazy-loaded content, so implementations should not depend on user interaction and should expose resource URLs in rendered HTML. Scope: Google Search guidance for JavaScript-driven lazy loading. Confidence: high · Verified: Google Search Central: Lazy-load content

That’s why the how of your implementation matters. Google’s doc lists three implementations it considers safe: the browser’s built-in lazy loading for images and iframes, the IntersectionObserver API (with a polyfill), or a JavaScript library that loads data as it enters the viewport. All three key off viewport intersection — not a scroll or click event that Googlebot will never fire.

The high-risk pattern is a custom or third-party JS lazy-load library. If the library misbehaves and the image URL never lands in the src attribute, Google simply won’t pick that image up — Splitt described exactly this failure mode on the SOTR episode. There’s nothing to index if the URL isn’t there. This is the same thing I flag in my JavaScript SEO guide: image lazy loading is usually fine, but lazy-loaded content is where indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. problems creep in, and the fix is to check what Google actually renders.

Infinite scroll is a different problem

Don’t conflate basic image/iframe lazy loading with infinite scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page. or paginated loading. Deferring images is one thing; loading new chunks of content as the user scrolls is another, and it needs its own architecture. Google’s guidance: give each chunk a persistent, unique URL, keep the content stable per URL (use absolute page numbers like ?page=12, not relative values like ?date=yesterday), and update the displayed URL with the History API as each chunk becomes the primary visible content so it can be refreshed, shared, and linked. Skip that and the deeper content behind an endless scroll may never be reliably crawled or indexed.

How to test it

The verification path is the same in Google’s doc and in my own methodology: use Search ConsoleGoogle's free tool for monitoring crawling, indexing, and search performance.’s URL Inspection ToolA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. and look at the rendered HTML. If your image (or video) URLs appear in the src attribute on the <img>/<video> elements in that rendered HTML, your setup works. Google says this outright — check the rendered HTML to make sure your content is in it. If the URL is missing from src, that’s your problem, and it usually points back to a scroll/click trigger or a broken library.

You can also lean on your web-performance tooling: PageSpeed Insights flags off-screen images you should be deferring. In my PageSpeed Insights guide at Ahrefs I note that the “defer offscreen elements” audit is telling you to lazy-load images — a handy way to connect the diagnostic you already run to the fix.

Ranking-signal honesty

Be clear about what lazy loading is and isn’t. Using it is not a direct ranking factor, and not using it isn’t a penalty. The connection to rankings is indirect: it flows through Core Web Vitals (mainly LCP, sometimes INP for iframes) and through crawlabilityCrawlability is how well search engine crawlers can discover, access, and fetch a site's pages. A crawlability issue is any technical condition — blocked access, broken links, server failures, or bloated URL inventory — that stops pages from reaching the index., if a bad implementation hides content. Splitt characterized the ranking effect through Core Web Vitals as a tiny, minute factor in most cases. So optimize lazy loading for your users’ load experience and for clean indexing — not because you expect a ranking bump from the attribute itself.

Where this fits

Lazy loading is one lever in the broader web performance toolkit. It pairs with resource hintsResource hints are <link> elements (or equivalent HTTP Link headers) that tell the browser to do network work — DNS lookups, connection setup, or fetching a resource — earlier than it would discover the need on its own. The main ones are dns-prefetch, preconnect, preload, modulepreload, and prefetch. (preload/preconnect for the resources you do want early), font-loading strategy, caching, and a CDN — and it’s judged, ultimately, through Core Web Vitals. Get the above-the-fold/below-the-fold split right and it’s one of the cheapest wins available.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.