How Googlebot Crawls Single-Page Applications: What Breaks and Why
Your React app looks complete in the browser. Run a curl against the same URL and you get a near-empty HTML shell with a script tag. That gap is where SPA SEO breaks down, and why teams running React, Angular, or Vue often find their pages indexed late, ranked poorly, or missing entirely. This guide explains how Googlebot actually processes a single-page application, where the pipeline fails, and what to do so your content gets indexed instead of stranded in a shell.
The confusion comes from assuming Google renders like a user. It can, but the process is staged and expensive, and it does not always complete the way a fast browser does. A page that depends on JavaScript to reveal its content is asking the crawler to do extra work, and when that work is slow, incomplete, or skipped, the index reflects the shell, not the page. Understanding the stages is how you stop blaming "Google" and start fixing the actual gap.
The Two-Wave Crawl Process
Googlebot fetches the HTML in Wave 1, then executes JavaScript to render the page in Wave 2, which can happen later, sometimes much later. If Wave 1 returns only a shell, the crawler's first impression of your page is empty, and the content only appears after rendering. That delay is why SPA pages often show as "Discovered - currently not indexed" for long stretches: the crawler fetched the shell, queued rendering, and has not yet gotten to it, or gave up because rendering was too costly.
Why Rendering Fails or Is Delayed
Several things break Wave 2. A shell with no meaningful HTML means there is little for the crawler to hold onto while it waits. A heavy JavaScript bundle makes rendering slow, so the crawler processes fewer of your pages per day. A render that throws an error on the crawler's engine leaves the content absent even though your browser shows it. Each failure produces the same symptom: an indexed shell, not an indexed page, and the fix is to remove the dependency on rendering for the content to exist.
- Empty shell: Wave 1 has no content, so indexing waits on rendering.
- Heavy bundle: Slow render means fewer pages processed per day.
- Render error: A throw on the crawler engine drops the content entirely.
- Lazy data: Content fetched after render may be missed if timing is off.
Server-Side or Pre-Rendering Fixes It
The reliable fix is to return real HTML with the content already present, via server-side rendering or pre-rendering for the routes you want indexed. Then Wave 1 already contains the page, rendering becomes a Progressive enhancement rather than a requirement, and indexing happens fast. This single change resolves most SPA indexing problems, because it removes the crawl dependency on JavaScript execution for the content to exist at all.
Verify with URL Inspection
Do not guess. Use the URL Inspection tool, fetch as Google, and compare the rendered HTML to the live page. If the rendered version is missing headings or body text, that is your gap. The "Discovered - currently not indexed" status with a thin Wave 1 fetch is the signature of a crawl that gave up before rendering, and it points you straight at the shell as the problem.
A Worked Example
An Angular app showed most routes as "Discovered - not indexed" and ranked for none. Inspection revealed an empty Wave 1 shell and a three-megabyte bundle. The team added pre-rendering to the top routes and trimmed the bundle. Within two months, indexed routes rose past nine hundred and organic traffic tripled, because the crawler now received real HTML in Wave 1 and rendered the rest fast enough to keep up.
Common SPA Crawl Mistakes
The first mistake is assuming Google sees what the browser sees and ignoring the shell. The second is shipping the full app bundle to every route, making rendering slow. The third is never inspecting as Google, so the team blames rankings instead of the empty Wave 1 fetch. All three are fixable without rebuilding the product, and all three are invisible until you look at the render.
Frequently Asked Questions
Does Google Render Javascript Spas?
It can, in a second wave, but slowly and imperfectly. Relying on it leaves content unindexed or delayed; pre-rendering or SSR is the dependable fix.
Why Are My SPA Pages "Discovered - Not Indexed"?
Usually because Wave 1 returned an empty shell and rendering was delayed or failed. Inspect as Google to confirm the gap, then pre-render the key routes.
Will Pre-Rendering Hurt the User Experience?
No. Pre-rendered HTML is replaced by the interactive app on load; users get the same experience with faster first paint and proper indexing.
Key Takeaways
- Googlebot crawls in two waves; SPA content depends on the second.
- An empty Wave 1 shell delays or prevents indexing.
- Heavy or erroring JavaScript makes rendering slow or fail.
- Server-side or pre-rendering returns real HTML and fixes most issues.
- Inspect as Google to see the shell gap directly.
- Trim the bundle so rendering keeps pace with crawl budget.
Make Rendering Cheap and Robust
Beyond returning HTML, make the render itself resilient. Avoid JavaScript that only the latest browser engines support, defer third-party scripts that can throw, and keep the critical content out of a data fetch that might race the crawler. A page that renders quickly and without error on a strict engine is a page the crawler processes reliably, which directly raises how many of your routes reach the index. Robustness here is not a nicety; it is the difference between indexed and stranded for an SPA.
Monitoring as an Ongoing Practice
A one-time fix regresses as the app changes, so monitor the crawl. Re-inspect key routes as Google after each significant deploy, watch the "Discovered - not indexed" count, and alert on a rise the way you alert on server errors. An SPA that stays indexed is the one whose team treats crawler rendering as production behavior, not a separate SEO chore. The monitoring is what catches the regression in a week instead of after rankings have already slipped for a quarter.
Do the Curl Test Today
Run a curl on your top route and read what comes back. If it is a shell with a script tag and no content, you have found the gap without any tooling, and you know pre-rendering is the fix. The curl test is the thirty-second check that tells you whether indexing is at risk before rankings move, and every SPA team should run it on their key routes quarterly. Seeing the empty shell directly is what makes the problem real enough to fix, instead of an abstraction you keep meaning to investigate.
Red Flags Your SPA Will Not Index
The symptoms are consistent. A curl on your route returns an empty shell with only a script tag, meaning Wave 1 has no content. The Coverage report shows routes stuck in "Discovered - currently not indexed" for weeks. The rendered HTML in inspection is missing headings or body text your browser shows. And a heavy or erroring bundle makes rendering slow or fail on the crawler's engine. Any of these means your content lives only in JavaScript, and the fix is the same: return real HTML through pre-rendering or server-side rendering, keep the bundle lean, and inspect as Google so the shell gap is visible before rankings slip.
The Bottom Line
Googlebot can render SPAs, but not as fast or as reliably as a browser, and content that lives only in JavaScript risks never being indexed. Return real HTML through pre-rendering or SSR, keep the bundle lean, and inspect as Google to see the shell gap. Do that and your SPA pages index on the crawler's schedule instead of stranded in an empty fetch.