JavaScript SEO: How Google Renders and Indexes JS-Heavy Pages
A technical guide to JavaScript SEO: how Google's crawl-render-index pipeline handles JS, the failure modes it introduces, and how to serve indexable content.
Google handles JavaScript in a three-stage pipeline — crawl, render, index — and each stage can silently drop content the previous one never saw. The crawler reads only your raw HTML; rendering happens later, on a separate queue, in a headless Chromium that has hard limits. Only the first 15 MB of any resource is fetched. If your content or links appear only after JS runs, they enter that gauntlet, and some of them do not survive it.
The crawl-render-index pipeline
Understanding JavaScript SEO starts with the order of operations. Googlebot fetches a URL and parses the raw HTML first. It queues every <a href> it finds and, if the response is HTTP 200, passes the page forward. Nothing that requires JavaScript exists at this point.
The page is then queued for the Web Rendering Service (WRS), an evergreen headless Chromium. WRS executes your JavaScript, the DOM mutates, and a rendered HTML snapshot is produced. That snapshot is what gets indexed — and any links discovered during rendering feed back into the crawl queue for the next pass. The render queue delay is usually short, but it is not guaranteed. On a large or frequently changing site, that lag interacts with your crawl budget: pages waiting to render are pages not yet indexed.
The practical takeaway: raw HTML and rendered HTML are two different documents, and Google acts on both. Crawlinx exists partly to show you the gap between them.
What Googlebot will not do
WRS is a real browser, but it is not a user. Several behaviors that work fine for humans are invisible to Google.
It does not click. Googlebot executes JavaScript but never triggers user-action events. Content behind an onclick handler, a tab that loads on interaction, or a "load more" button is not seen.
It does not scroll. All primary content must be present when the page loads or when it enters the viewport — do not defer it behind a scroll event. Native loading="lazy" and IntersectionObserver are fine; a scroll listener that fetches your article body is not.
State does not persist. WRS is wiped between page loads. localStorage, sessionStorage, and cookies do not carry across navigations. Never route or gate content on them.
Not every API exists. WebGL, WebSockets, WebRTC, and camera access are not guaranteed. Feature-detect and provide a fallback that still renders your content.
SSR vs CSR: pick a rendering strategy
Pure client-side rendering (CSR) ships an empty shell and builds the page in the browser. It works, but it puts everything on WRS and the render queue, and it leaves nothing for crawlers with weaker JS support. Bing renders JS far less reliably than Google — do not assume BingBot runs your SPA. Server-rendered HTML matters more, not less, for Bing.
Prefer one of these:
- Server-side rendering (SSR): HTML generated per request. Next.js app router,
getServerSideProps, Astro SSR. - Static rendering / prerendering: HTML built ahead of time. Next.js SSG, Astro static output.
- Hydration: ship real HTML, then attach interactivity in the browser.
Dynamic rendering — serving a prerendered version to bots only — is deprecated. Google calls it "a workaround, not a long-term solution." Do not build new architecture on it.
Routing, titles, and metadata
Routing must use the History API (pushState) with real, crawlable <a href> URLs. Hash fragments (#/products) are dead — the old AJAX-crawling scheme is gone, and Google will not treat them as separate pages. Links written as href="#" with an onclick handler are invisible, which is a direct hit to your internal linking.
Every route needs a unique <title> and meta description. JavaScript can set document.title, and WRS captures the result — but a title that changes between raw and rendered HTML is a signal worth auditing, because Google may act on either version.
Common JavaScript SEO failure modes
- Blocked resources. If
robots.txtdisallows your JS or CSS, WRS cannot render the page and the content vanishes. Never block the assets required to paint the page. noindexin raw HTML. You cannot reliably removenoindexwith JavaScript. When Google seesnoindexin the raw source, it may skip rendering entirely. If you want a page indexed, it must never carrynoindexin its original code.- Injected canonicals. Prefer a server-set
rel=canonical. JS injection is fragile and easily produces conflicting or duplicate canonical tags; changing the canonical after render is a well-known pitfall. - Soft 404s in SPAs. When content is missing, redirect to a real 404 URL or inject
noindex— do not return a 200 with an empty shell. - Cache mismatches. WRS caches resources aggressively and may ignore your cache headers. Use content fingerprinting (
main.2bb85551.js) so updated assets get new filenames.
One more thing: client-side analytics undercount Googlebot. Do not use them to judge crawlability. Use Search Console Crawl Stats and URL Inspection — the Rendered HTML view confirms exactly what Google produced.
How Crawlinx helps
Crawlinx fetches both the raw HTML and the rendered DOM, then diffs them — its core differentiator for JavaScript SEO. The render-diff checks flag:
- body content that only appears after JavaScript runs
- links that exist only after rendering
- a title that differs between raw and rendered HTML
- a canonical injected or changed by JS
- a robots meta tag altered by JS
- pages that depend on JavaScript to show their content
Related structural checks cover the surrounding failure modes: blocked JS/CSS or pages, noindex present in the raw HTML, and a missing canonical.
Takeaway
JavaScript is not a ranking problem; it is a delivery problem. Google's crawler reads raw HTML, renders on a delayed queue with real limits, and indexes what survives — while Bing may not render at all. Serve crawlable HTML through SSR or static rendering, keep routing and metadata in real markup, and diff raw against rendered to catch what only your users can see.
Audit your own site — free
77 checks, internal PageRank, render-diff. No signup, results in ~30s.