Your Lazy Loading Is Hiding Content from Googlebot

On this page

If your below-the-fold content is missing from the index, the likely cause is that it only enters the DOM when a user scrolls, and Googlebot does not scroll. Googlebot renders JavaScript, but it does not simulate scrolling, clicking, hovering, or typing. So content injected by a raw scroll event, or by a “load more” click, fires for a human and never fires for the crawler, which captures only what its render produced and sees placeholders or empty space where the rest should be. The cure is to put all indexable content in the initial HTML or server-rendered output, and to lazy-load only images, with a real src present in the DOM. Scroll-event loading is fine for media; it is broken for content.

The mechanism

Google’s rendering pipeline loads the page, executes its JavaScript, and indexes the rendered result. It does not interact with the page beyond that. To compensate for not scrolling, Googlebot renders with a very tall viewport, so content set to load when it enters the viewport tends to load because almost everything sits within that tall window. This is why Google actually recommends the IntersectionObserver approach for lazy loading: it keys off viewport visibility, which the tall render satisfies, rather than off a user action. The failure case is narrower: if a section’s content is added to the DOM only after a real scroll event, a scroll-depth threshold the tall viewport never reaches, or a click, then Googlebot’s render finishes before that trigger fires and the content is simply absent. Infinite scroll is the common form of this and still needs the structural fix below.

This is the load-bearing distinction from the accordion case. Accordion content is in the DOM and indexed but weighted lower because it requires a click. Scroll-injected content is not in the DOM at all, so there is no weighting question; it does not exist as far as indexing is concerned. One is underweighted; the other is missing.

The critical distinction: content versus images

The phrase “lazy loading” covers two different things, and only one is dangerous.

Lazy-loading content out of the DOM until scroll is broken for SEO. The product cards, the article body sections, the listing items are not present in the rendered HTML, so they cannot be indexed.

Lazy-loading images that are already in the DOM with a real src is fine. The native loading="lazy" attribute on an <img> or <iframe> is supported by Googlebot’s rendering engine and is the recommended approach. The element and its src are in the markup; the browser defers fetching the file until it is near the viewport. The content (the image reference) is present; only the byte transfer is deferred. That is the right way to use lazy loading, and it costs you nothing in indexing.

The failure is reaching for the image technique and applying it to content, so that the content itself is gone from the DOM rather than merely the image bytes being deferred. A quick way to keep the two straight: ask whether the text a user reads is in the markup before any scroll. If the words are present and only the picture loads late, you are fine. If the words themselves appear only after a scroll handler fires, you have moved content out of the DOM and into an event that Googlebot will never trigger.

The SSR trap

Teams who “already do SSR” assume they are safe, and often are not. The trap is server-side rendering that merely replicates the client’s initial render, emitting only the first N items because that is what the client shows before the user scrolls. If your SSR outputs the first 12 of 60 products because the client component renders 12 on mount, then your indexable ceiling is 12, no matter how many the page eventually shows a scrolling user.

SSR must output all indexable content regardless of what the client-side enhancement does afterward. The server render is the indexable surface; it should not inherit the client’s pagination-on-scroll behavior. Check this explicitly, because it is invisible in a browser, where scrolling fills in the rest.

The fix is to decouple the server render from the client’s initial-mount count. The server should emit the full set of indexable items, and the client can then hydrate, virtualize, or progressively reveal them for performance without removing any from the markup that Googlebot reads. If your framework’s default is to server-render exactly what the first client paint shows, you have to override that default for the content that needs to be indexed. View-source on the production URL and count the items in the raw HTML, before any JavaScript runs, to see your true indexable ceiling rather than the post-scroll count a browser shows you.

The page-speed objection, answered

The usual reason for scroll-loading content is performance: “we cannot ship 60 product cards in the initial HTML.” But the HTML of a product card (title, price, link, a few attributes) is light text that compresses well over the wire. What actually weighs on the page is the images, and those you lazy-load correctly with loading="lazy" and a real src. So you can have both: all the card content in the initial render for indexing, and the images deferred for speed. The performance argument does not justify removing the content from the DOM, because the content was never the heavy part.

Infinite scroll needs crawlable paginated URLs

For long lists and feeds, pair the infinite scroll with parallel paginated URLs that each render their content server-side: ?page=2, ?page=3, and so on, reachable as real URLs that return their items in the HTML. The infinite scroll remains the user experience; the paginated URLs are the crawl path. Pure JavaScript-state infinite scroll, where later pages exist only as in-memory state with no addressable URL, exposes only the first load to Googlebot and hides everything past it.

Wire the two together so the experience and the crawl path stay in sync: as the user scrolls and the next batch loads, the corresponding paginated URL should exist and return that same batch when requested directly. Then link those paginated URLs somewhere crawlable, even if only in the page’s underlying markup, so Googlebot can discover and request page two without scrolling. The principle is that every item you want indexed must be reachable through a URL that returns it in server-rendered HTML, independent of any scroll the user does or does not perform.

Verification, and the cloaking warning

Inspect the URL in Search Console and read the rendered HTML, then count the items actually present. If the page shows 60 to a scrolling user but the rendered HTML contains 8, you have found your ceiling. The Mobile-Friendly Test that older guides recommend for this was retired in December 2023; use URL Inspection’s rendered HTML instead.

Do not solve this by serving Googlebot a different, fully-loaded version keyed on user-agent. That is cloaking, and it is the wrong fix on top of being a policy violation. Note also that dynamic rendering, the bot-specific prerendering workaround, is now classified by Google as a deprecated workaround rather than a recommended solution, so the answer is not to detect the bot and feed it special HTML. The answer is to fix the architecture so the same response, served to everyone, already contains the indexable content.

Frequently Asked Questions

Is loading="lazy" on images bad for SEO?

No. Native loading="lazy" on images and iframes is supported by Googlebot and recommended. The element and its src are in the DOM; only the file fetch is deferred. The problem is lazy-loading content out of the DOM, not lazy-loading image bytes.

How do I confirm content is missing rather than just slow?

Use URL Inspection in Search Console and read the rendered HTML. Count the items present. If a scrolling user sees far more than the rendered HTML contains, the missing items are absent from the index, not merely deferred.

Sources

Google Search Central, Fix lazy-loaded content: https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading
Google Search Central, Understand the JavaScript SEO basics (rendering, no scrolling): https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics
Google Search Central, URL Inspection tool (rendered HTML): https://support.google.com/webmasters/answer/9012289