← All posts

JavaScript and Indexing: Why Google Can't See Your SPA

JavaScript and Indexing: Why Google Can't See Your SPA

Open your single-page app in a browser and everything is there: navigation, product grid, copy, prices. Open the same URL as raw HTML and there is a <div id="root"></div> and a script tag. That gap is the whole story of JavaScript SEO indexing. Google does not index what your framework eventually paints on screen. It indexes what it can fetch, render, and process on its own schedule, and JavaScript inserts an extra stage into that process where pages quietly stall.

This post explains what Googlebot actually does with a JavaScript-heavy page, the specific ways SPAs fail at each stage, and how to find out which stage yours is failing at before you rewrite anything.

What Googlebot does with a JavaScript page

Google’s own documentation describes three phases for JavaScript content: crawling, rendering, and indexing. For a plain HTML page, crawling and indexing sit close together: fetch the HTML, parse the content, decide. For a JavaScript page there is a stage in between. Googlebot fetches the initial HTML, notices the content is not in it, and places the page in a render queue. When resources allow, a headless Chromium renders the page, executes your JavaScript, and only then does the rendered result move on to indexing.

Two consequences follow. First, rendering is deferred, not immediate. Google states that a page waits in the queue until resources allow rendering, and Google does not publish how long that takes for any given site. Second, rendering can fail in ways a browser session never shows you: a blocked script, a timed-out API call, an error thrown only in a headless environment. When that happens, what reaches the index is the empty shell, not the app.

Indexing was never promised in the first place. Crawling does not oblige Google to index a page; rendering does not either. A JavaScript page simply has more stages at which the decision can quietly go against you.

Where SPAs actually fail

Most JavaScript indexing problems fall into a small set of patterns.

Empty initial HTML. Pure client-side rendering ships a shell and builds the page in the browser. Google can handle this, but you are betting every page on the render queue and on your scripts executing cleanly in an environment you cannot watch. Sites like this often accumulate URLs sitting in a “Crawled - currently not indexed” state: fetched, but never judged worth keeping.

Links that are not links. Googlebot discovers URLs through <a href="..."> elements. Navigation built from onclick handlers, buttons, or router calls without a real href is invisible to crawling. If your category pages are only reachable through JavaScript navigation, Google may never request them at all.

Blocked resources. If robots.txt disallows your /static/ or /assets/ directory, the renderer cannot fetch the very scripts that build the page. The HTML is crawlable, the page renders blank, and nobody gets an error message. It is the same class of trap covered in the robots.txt disallow post, with a JavaScript-shaped twist.

Client-side error pages. An SPA that handles missing routes by rendering a “not found” component over a 200 response creates soft 404s at scale. Google sees a thin page claiming nothing exists and treats it accordingly, a failure mode close to the ones in the soft 404 guide.

Content behind interaction. Anything that appears only after a click, a scroll, or a consent banner dismissal does not exist for indexing. The renderer does not click, and it does not scroll the way a person does.

Metadata set too late. Titles, canonicals, and meta robots tags injected by JavaScript are only seen at render time, if at all. A canonical that appears only after hydration is a suggestion Google may never read.

How to see what Google sees

Do not guess. Three checks, in order of effort.

First, fetch the raw HTML: curl the URL, or view source rather than the DOM inspector. If your primary content, title, and internal links are present in that response, you are largely out of the danger zone. If you see an empty root element, everything depends on rendering.

Second, use the URL Inspection tool in Search Console on a handful of representative URLs. The rendered HTML screenshot and the crawled page view show whether Google’s renderer produced your content or a shell, and which resources it could not load.

Third, check outcomes rather than mechanics. Rendering successfully is not the same as being indexed, and this is where JavaScript sites most often mislead themselves. A page can render perfectly and still be declined. The only evidence that matters is whether the URL is actually returned in Google’s results, which is a check worth running on a schedule rather than once. This is the part AIndexify does: submit your URLs, and each one is checked against live Google results at fixed intervals, reporting submitted and indexed as two separate events with the date the page was actually found. For a rendering problem, the pattern is diagnostic. If submissions deliver but pages sit unindexed for the full check window, your problem is not discovery, it is what Google finds when it gets there.

Fixing it: move the work to the server

The reliable fixes all share one idea: put the content in the initial HTML so indexing does not depend on the render queue.

Server-side rendering (SSR) generates the full HTML per request and hydrates it in the browser. Static generation (SSG) does the same work at build time, which suits content that changes on deploys. Frameworks make both mainstream now; the trade-offs for Next.js specifically are covered in the Next.js indexing post. Pre-rendering services that serve a rendered snapshot to crawlers still exist, but they add a second version of every page that you must keep honest.

Whichever route you take, keep the fundamentals boring: real href links, correct status codes for missing content, metadata in the server response, and no robots.txt rules blocking your own scripts. The broader checklist lives in the complete Google indexing guide.

A rendering fix is finished when the index says so, not when the deploy goes green. Submit the affected URLs, watch whether “submitted” turns into “indexed” over the following days, and you will know whether the fix worked. See where your pages actually stand: submit your URLs and let the checks report what Google decided.

Sources