Does Googlebot Scroll and Click in SPAs?
Developers often assume Googlebot behaves like a patient user — scrolling to the bottom of a feed, clicking “load more,” opening tabs to read what is inside. It does not. Understanding exactly what Googlebot interacts with is essential for anyone building a single-page app, and it is a core part of understanding Googlebot’s rendering pipeline. The short answer: Googlebot renders, but it does not interact.
Problem Statement
Googlebot’s rendering step loads your page, executes JavaScript, waits for the network to settle, and snapshots the DOM. What it does not do is simulate a human. It does not click buttons, tap, hover, submit forms, or scroll the way a person drags a scrollbar. Any content that only appears as a consequence of one of those gestures is invisible to indexing — it is simply not in the DOM at the moment the snapshot is taken.
This trips up three patterns constantly: content behind a click-to-load button, items appended by infinite scroll as the user reaches the bottom, and panels that fetch their contents only when a tab or accordion is opened. In each case the page looks fully populated to a human who interacts with it, and completely empty to a crawler that does not. The fix is not to make Googlebot behave like a user; it is to make the content present without the interaction.
Step-by-step fix
-
Inventory your interaction-gated content. Walk each template and mark everything that only appears after a click, tab, hover, form submit, or scroll-to-bottom. These are your at-risk items — content a crawler will never trigger.
-
Render content at load, hide it with CSS if needed. Googlebot reads content that is in the DOM even when it is visually hidden with
display:noneor an off-screen class. Move tab and accordion contents into the initial render and toggle visibility client-side, rather than fetching them on open. -
Back infinite scroll with real paginated URLs. Keep the scroll experience for users, but ensure each batch of items also exists at a crawlable URL —
/feed/?page=2or/feed/page/2/— that renders its items on load. Link those pages with anchor elements so Googlebot can follow them. -
Replace click-to-load with load-then-reveal. For “load more” patterns, fetch the first meaningful set of items during the initial render so the primary content is present without a click. A button can still load additional items for users beyond that set.
-
Use anchor elements for navigation. Googlebot discovers URLs from
hrefattributes on real anchor tags. Adivwith a click handler that routes client-side is not a discoverable link; give every navigable destination a genuine anchor with anhref.
The pagination fix in step 3 is the difference between a feed that exposes only its first batch and one whose every batch sits at a crawlable URL.
Validation
Confirm the at-risk content is in the rendered DOM without any interaction, exactly as Googlebot would see it.
// interaction-independent-render.mjs — verify content exists without clicking or scrolling.
import { chromium } from 'playwright';
const GOOGLEBOT_UA =
'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Mobile Safari/537.36 ' +
'(compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
const url = process.argv[2];
const browser = await chromium.launch();
const page = await browser.newContext({ userAgent: GOOGLEBOT_UA }).then((c) => c.newPage());
// Render and snapshot WITHOUT clicking, scrolling, or hovering — mirror Googlebot.
await page.goto(url, { waitUntil: 'networkidle' });
const feedItems = await page.locator('[data-feed-item]').count();
const tabPanelText = await page.locator('#reviews-panel').innerText().catch(() => '');
const nextPageLink = await page.locator('a[rel=next]').count();
console.assert(feedItems > 0, 'FAIL: no feed items present without scrolling');
console.assert(tabPanelText.trim().length > 0, 'FAIL: tab panel empty without a click');
console.assert(nextPageLink > 0, 'FAIL: no crawlable next-page anchor for the feed');
console.log(`feed items at load: ${feedItems}, next-page anchors: ${nextPageLink}`);
await browser.close();
Because the script never calls click() or scrolls, a passing run proves the content is reachable exactly as Googlebot renders it. Cross-check by inspecting the same URL in Search Console’s “View crawled page” — the rendered HTML there should contain the same items.
Common Pitfalls
- Assuming Googlebot scrolls to load more. It renders with a tall viewport, which fires some lazy loaders, but it does not perform scroll gestures. Content that depends on reaching the bottom of a list is unreliable at best.
- Fetching tab or accordion content on open. If the panel’s data is requested only when the user opens it, the crawler never opens it and never sees the data. Render it at load and hide it with CSS.
- Routing through non-anchor elements. A clickable
divis not a link Googlebot can follow. Navigation must use anchor elements with realhrefvalues. - Relying on hover or focus to reveal text. Googlebot does not hover or focus. Any content that only appears on those events is missed.
- Confusing “visible” with “indexable.” CSS-hidden content in the DOM is indexable; interaction-gated content absent from the DOM is not. Presence in the rendered DOM is what counts.
That last pitfall is the whole decision in one question: not “is it visible?” but “is it in the rendered DOM at load?”
Related
- How to check if Googlebot executes your JavaScript — confirm the render runs at all.
- Fixing crawl budget waste on infinite scroll SPAs — the paginated-URL fix in depth.
- How the Web Rendering Service queue delays indexing — when the snapshot is actually taken.
Frequently Asked Questions
Does Googlebot scroll down a page to load lazy content?
Googlebot does not scroll like a user. It renders with a tall virtual viewport so that content lazy-loaded on scroll often triggers anyway, but content that depends on real scroll-position events or on reaching the bottom of an infinite list is unreliable. Anything that only loads after a genuine scroll gesture may never enter the rendered DOM.
Does Googlebot click buttons or tabs to reveal content?
No. Googlebot does not click, tap, hover, or perform any user interaction. Content hidden behind a click-to-load button, a tab, or an accordion that fetches on open will not be seen unless it is present in the rendered DOM at load time, even if visually hidden with CSS.
How do I make infinite-scroll content indexable?
Back the infinite scroll with real paginated URLs that render their items server-side or on load, and link them with anchor tags Googlebot can follow. The scroll experience stays for users, while each page of items is independently crawlable at a stable URL rather than depending on a scroll event that Googlebot never fires.