Googlebot does render your JavaScript, just in a second pass, on its own schedule, with a stripped-down Chromium that carries no cookies and waits only so long before moving on. That second pass is where most JavaScript SEO problems live.
I have audited dozens of React and Vue and Next.js sites for clients who were convinced "Google can't see JavaScript." That hasn't been true since 2019, when Google switched to an evergreen Googlebot running modern Chromium. The actual problem is more boring and more fixable: the site ships an empty initial HTML, then asks Googlebot to wait. Sometimes Googlebot waits, sometimes it doesn't.
This post is what the Google Search Central docs and recent Search Off the Record episodes actually say about rendering, and what to fix on a JS-heavy site.
The two-pass rendering model
Per Google's JavaScript SEO basics, Googlebot processes a JS-powered page in three phases: crawl, render, index. Crawl and render are not the same step.
In phase one, Googlebot fetches the URL and reads the response, parsing any HTML it gets, looking for href attributes to add to its crawl queue, and checking the robots meta tag. If the initial HTML is empty (just a <div id="root"> and a script tag) there is nothing here to index yet.
In phase two, every 200-response page goes into a render queue where a headless Chromium picks it up, executes the JavaScript, and produces the rendered HTML. Google parses that HTML for links and content, then indexes it.
> Googlebot queues every page with a 200 status code for rendering, regardless of whether the page actually uses JavaScript. The page may sit in that queue for seconds, or longer.
The render queue is the bottleneck nobody talks about. If your page only has content after JavaScript runs, you are betting on that queue. For a small site, that bet usually wins. For a fast-changing site (listings, real-time inventory, news) that bet costs you indexing latency.
Why the "Google can't see my SPA" panic is mostly wrong
The old fear came from a real era. Per Martin Splitt and Edu Pereda on Search Off the Record episode 58, the original "Google can't render JS" sentiment dates back to when rendering was slow and unreliable. Since the evergreen switch, the rendering engine is current Chromium with the full set of standard browser APIs.
What it does not support, per Google's troubleshooting guide: user-permission features (camera, geolocation), state across page loads (localStorage and cookies are wiped per fetch), and non-HTTP transports (WebSockets, WebRTC). If your content depends on any of those, the rendered HTML will be empty.
That is the real failure mode. The problem isn't "JavaScript broken," it's "content gated behind something Googlebot literally cannot do."
Three patterns that quietly kill indexing:
- Content that only loads after a click ("Load more," "Show details," tabbed interfaces with no DOM-level content)
- Content that requires a cookie set on the previous page
- Content fetched from an API that fails the second request because the session is gone
If a real human can see the content on page load with no interaction, Googlebot probably can too. If they have to click first, Googlebot does not.
How browsers (and Googlebot) actually parse HTML
Per Gary Illyes and Martin Splitt on SOTR episode 105 ("How Browsers Really Parse HTML"), there is a parsing rule most developers do not know: a non-metadata element appearing in the <head> implicitly closes the head and opens the body. Anything after that point (your canonical, your hreflang, your robots meta) ends up inside the body and is silently ignored.
The pattern that triggers this in the wild: an inline script in the head that injects an iframe or a paragraph during render. Gary cited a real example where hreflang tags appeared correct in source but were ignored because an injected iframe had already closed the head.
> Metadata elements (<link>, <meta>, <title>) only count when they are inside the head. A stray <p> or <iframe> in the head terminates the head context, and any subsequent metadata is treated as body content and discarded.
This is one of the strongest arguments for putting your canonical, hreflang, and robots meta into the server-rendered HTML rather than injecting them later via JavaScript.
Use the History API, not URL fragments
A single-page app that routes by URL fragment (example.com/#/products) is a dead end for indexing. The AJAX crawling scheme was deprecated in 2015; Googlebot does not resolve fragment URLs to distinct pages.
The fix is the History API. Real URLs in href attributes, intercepted in JavaScript, with window.history.pushState updating the visible URL. The Google docs example is short and worth copying.
The same rule applies to lazy-loaded content. Use loading="lazy" for images and iframes. For content blocks, use the IntersectionObserver API to load when the element enters the viewport. Do not rely on scroll or click handlers, because per Google's lazy-loading guidance, Search does not interact with your page. It doesn't scroll or click.
For paginated content under infinite scroll: give each chunk a real, persistent URL (?page=12, not ?date=yesterday), link to those URLs sequentially, and update the visible URL via History API as new chunks become primary. That is the only way a 200th-position result becomes individually crawlable.
The canonical-via-JavaScript trap
This is the most common JS SEO bug we see when we audit client sites. The HTML ships with one canonical. JavaScript runs and injects a different canonical. Now Google has two signals pointing in opposite directions.
Per Martin Splitt on SOTR 105: "If you have a canonical that is there at the first time we fetch the HTML from the server and then the JavaScript changes it, we actually advise against doing that." The fix is simple to state and easy to skip: set the canonical in the server HTML, or set it only in JavaScript, never both and never conflicting.
If your CMS or React framework supports server-side rendering of the canonical link tag, use it. If it does not, ship empty in HTML and inject from JS only.
SPA-heavy verticals where this matters most
Two industries we work with hit JS SEO walls constantly. Real estate sites lean on map components and filter UIs that load listings client-side after a user interaction, so Googlebot sees an empty grid. AV installers ship portfolio galleries built as carousels with content lazy-loaded behind clicks, then wonder why their case studies do not rank.
For both, the fix is usually the same: server-render the content card body. Keep the fancy interaction client-side. The HTML response should answer the question "what's on this page" before any JavaScript runs.
The soft-404 problem in SPAs
Client-side routing breaks HTTP status codes. A user navigates to /product/this-was-deleted, the SPA loads, the API returns "not found," and the page renders an error state, but the HTTP status is still 200. Google indexes it as a real page. Now you have ten thousand thin error pages in the index.
Per Google's docs, two fixes work: either JavaScript-redirect to a URL that returns a real 404 from the server (window.location.href = '/not-found'), or dynamically inject a <meta name="robots" content="noindex"> when the API says the resource is gone. Either one stops the bleed, so pick whichever your stack makes easier.
What to check before you call it done
Use the URL Inspection Tool in Search Console or the Rich Results Test. Both run the page through Google's actual rendering pipeline and show the post-render HTML. If your content is not in that rendered HTML, Google will not index it. No amount of theorizing fixes this, so open the tool and look at what came back.
While you are there: check the JavaScript console output the tool exposes. If something threw an error during render, that is where you find out. Logging window.onerror to your analytics is the move; per Google, that catches most runtime issues. Parse errors will not show up, but the rest will.
JavaScript SEO comes down to two things you can verify in the URL Inspection tool: the initial HTML gave Google enough to crawl with, and the rendered HTML gave Google everything it needed to index. If either one comes back short, you have your fix list.
Sources
- Understand JavaScript SEO Basics (Google Search Central, March 2026)
- Fix Search-Related JavaScript Problems (Google Search Central, December 2025)
- Fix lazy-loaded content (Google Search Central)
- Dynamic Rendering as a Workaround (Google Search Central, deprecated workaround, kept for reference)
- The new evergreen Googlebot (Google Search Central Blog)
- How Browsers Really Parse HTML, Search Off the Record episode 105 (Gary Illyes and Martin Splitt, February 2026)
- JavaScript at Google, Search Off the Record episode 58 (Martin Splitt, Edu Pereda, Pascal Birchler)