WriteMySEO / Blog / Infinite Scroll That Search Engines Can Actually Crawl
SEO technology

Infinite Scroll That Search Engines Can Actually Crawl

Scroll-triggered loading is invisible to crawlers. The paginated-URL fallback pattern, done with the History API, keeps the experience and the crawl.

Infinite scroll is a fine user experience and a terrible crawler experience, and the reason is mechanical, not mysterious. A crawler fetches a URL, renders it once, and records what it got. It does not scroll. Everything your page loads in response to a scroll event simply never happens during a crawl.

The result is predictable: a category with 800 products where search engines can see 24 of them, and every item past the first viewport-load reachable only through the sitemap — discovered, maybe, but stripped of the internal links that tell a search engine it matters.

Why crawlers never see your second batch

Google has documented that Googlebot does not interact with pages the way a user does — it does not click buttons and it does not scroll through content, though it renders with a large viewport that can trigger some lazy-loading implementations. That caveat is narrower than it sounds. Content loaded by an IntersectionObserver watching for viewport entry may fire during rendering; content loaded by a listener waiting for actual scroll events, or by a "Load more" button waiting for a click, will not.

Relying on the tall-viewport behavior is fragile even where it works: it is a rendering implementation detail, it varies with how much content you load per batch, and other crawlers — Bing, and the AI crawlers now fetching your pages — each have their own, generally less capable, rendering behavior. The reasonable engineering position is to assume no crawler scrolls, ever.

The paginated-URL fallback pattern

The fix Google has long recommended is not to remove infinite scroll but to back it with real pagination. Every batch of content corresponds to an ordinary, fetchable URL:

/products/           (items 1-24)
/products/?page=2    (items 25-48)
/products/?page=3    (items 49-72)

Each of those URLs must return its items as server-rendered HTML — or at minimum, HTML that renders without user interaction — and must contain plain <a href> links to the adjacent pages. A crawler that lands anywhere in the sequence can walk the whole thing with nothing but URL fetches, which is the only operation you can count on every crawler performing.

Component pages should each carry a self-referencing canonical. The common mistake is canonicalizing every page to page 1, which tells search engines the deeper pages are duplicates and quietly asks them to ignore the very URLs you built the fallback to expose. Page 2 is not a duplicate of page 1; it contains different items.

One historical note: rel="next" and rel="prev" annotations are optional. Google stated in 2019 that it no longer uses them as an indexing signal and had not for some time. They do no harm and other consumers may read them, but they are not the mechanism that makes this pattern work. The links in the HTML are.

Stitching the two together with the History API

The paginated URLs are the crawler's path. The History API is what lets human visitors keep the seamless scroll while staying on that same path.

As the user scrolls and batch N loads, call history.replaceState() (or pushState(), if you want each batch in the back-button stack) to update the address bar to the corresponding page URL:

// after appending items for page 3
history.replaceState({page: 3}, "", "/products/?page=3");

This buys you three things. The URL in the address bar always identifies what the user is looking at, so a shared or bookmarked link reproduces the user's actual position — served by the same paginated URL the crawler uses. Analytics record which depth users actually reach. And there is exactly one URL space; the crawlable version and the scrollable version are the same resource, not a parallel structure that drifts out of sync.

The inverse requirement matters just as much: loading /products/?page=3 directly must work, rendering that page's items server-side, with the infinite scroll continuing from there in both directions or at least forward.

Where implementations actually break

How to verify you got it right

Fetch a deep page with a plain HTTP client — curl, not a browser — and confirm the items and the prev/next links are present in the raw HTML. Then use Search Console's URL Inspection on the same URL and read the rendered HTML Google reports. Finally, check your server logs for Googlebot hits on ?page= URLs; a working implementation shows crawls walking the sequence within days or weeks, depending on your crawl frequency. Silence in the logs means discovery is not happening, whatever the rendering test said.

What to do

Inventory every scroll-triggered or click-triggered content loader on your site and ask one question of each: does an equivalent plain URL return this content as HTML with anchor links onward? Where the answer is no, build the paginated fallback first, wire the History API second, and treat the scroll behavior as an enhancement layered over a crawlable page structure — not the structure itself. Then verify in the raw HTML and the logs, because this is a pattern where the user-facing site can look perfect while the crawlable site is missing almost everything.

crawlingJavaScriptpagination

WriteMySEO produces marketing content, not legal, medical, financial, or compliance advice. Figures cited reflect publicly reported industry data at time of writing and shift over time.

Get started

We write this well about your industry, every month.

AI-drafted, human-reviewed SEO content on a flat subscription. Blog posts, metadata, schema, and internal links, shipped on a monthly rhythm.

See plans

More from the blog