SEO

JavaScript Rendering and Crawlability: Why Your SPA Isn't Being Indexed

AttractiveWebAI Team2026-09-045 min read

Run a crawler against a modern single-page app and you often get one page back: the homepage, with three links. The site has 400 pages. Nothing is broken in the browser. The crawler simply never saw them.

This is the most common reason a technically healthy site has almost no organic presence, and it is entirely fixable once you understand how rendering and crawling interact.

The two-wave model

A search engine processes a JavaScript page in two passes:

  1. Crawl and parse HTML. The raw response is fetched and links in it are extracted.
  2. Render. The page is queued for a headless browser that executes JavaScript, then the resulting DOM is parsed again and any new links are extracted.

The second wave is not guaranteed to be immediate. It depends on available rendering capacity and on how valuable the crawler considers the page. On large sites it can lag the first wave by days or weeks — and for low-priority pages it may effectively never arrive.

Everything that only exists after JavaScript executes inherits that delay and that uncertainty.

Where client-side rendering breaks discovery

Links that are not links. This is the big one:

<!-- Invisible to first-wave crawling -->
<div onclick="router.push('/products/123')">View product</div>
<a href="#" onclick="navigate('/products/123')">View product</a>
<button data-url="/products/123">View product</button>

versus:

<!-- Discoverable immediately -->
<a href="/products/123">View product</a>

A router-driven menu that renders <div> elements produces a site with no traversable link graph. Our own crawler recovers some of these by parsing onclick handlers and data-* attributes, which is a useful safety net — but building your navigation so a search engine needs that safety net is a choice with a real cost.

Content behind interaction. Tabs, accordions and "load more" buttons whose content is only fetched on click are frequently not in the rendered DOM either, because the crawler does not click.

Infinite scroll. If page 2 exists only after a scroll event, it does not exist for crawling. Provide paginated URLs alongside the scroll experience.

Client-only metadata. A title and description written by JavaScript after mount may be picked up in the second wave, but social crawlers and many non-Google engines never render at all — so your shared links show the generic app shell.

Choosing a rendering strategy

Strategy Indexing reliability Best for
SSG (static generation) Highest Marketing pages, docs, blog posts
ISR / on-demand revalidation High Large catalogues that change regularly
SSR (server rendering) High Personalised or frequently changing pages that must be indexed
CSR (client rendering) Low Dashboards, logged-in tools — pages that should not be indexed anyway

The decision rule is simple: if a page needs to rank, its content and links must be in the initial HTML response. Everything else is an optimisation.

A hybrid is usually correct. In a Next.js app, the marketing site, blog, category and product pages are statically generated or ISR-backed; the account area and dashboard are client-rendered and disallowed in robots.txt. There is no reason to pay server-rendering cost for pages you never want indexed.

Diagnosing it yourself in ten minutes

  1. View source, not inspect. Ctrl+U shows the raw HTML the server sent. Inspect shows the post-JavaScript DOM. If your product description is in the second but not the first, it depends on rendering.
  2. Disable JavaScript and browse. What is still navigable is what first-wave crawling sees.
  3. curl a page and count anchors:
    curl -s https://example.com/ | grep -o 'href="[^"]*"' | wc -l
    
    A homepage returning 3 links has a discovery problem regardless of what the browser shows.
  4. Compare crawl modes. In DeepAnalyzer, the standard fetch mode sees the raw HTML and Strong Mode runs a real browser. A large gap between the two — links found, headings found, content score — is a precise measurement of how much of your site depends on JavaScript.

That last comparison is worth doing before any replatforming decision, because it turns "our SPA might have SEO issues" into a number you can put in a ticket.

Fixes, in order of impact

Use real anchors. Framework <Link> components render real <a href> elements. Use them. Reserve programmatic navigation for genuine app interactions, not content links.

Server-render anything indexable. Content, headings, canonical, title, description, and structured data belong in the initial response.

Give every view a URL. Modal-only content, tab-only content and scroll-only content cannot rank because there is nothing to rank. If it deserves search traffic, it deserves an address.

Keep the rendered and raw DOM consistent. A canonical or noindex injected client-side that contradicts the server response is a coin flip.

Do not block your JS and CSS. Disallowing /static/ or /_next/ in robots.txt prevents rendering entirely, so the crawler is stuck with the empty shell.

A note on measurement

After a rendering fix, watch two things: the number of URLs discovered per crawl, and the ratio of "crawled – currently not indexed" in Search Console. Rendering fixes usually show up first as a jump in discovery, then as an indexing improvement two to four weeks later. If discovery does not move, the fix did not land where you thought it did — recheck the raw HTML rather than the browser.

FAQ

Does Google render JavaScript?

Yes, Googlebot renders with an evergreen Chromium. But rendering is queued and resource-limited, so it lags crawling. Other engines — Bing, Naver, Baidu — and most social preview crawlers render less reliably or not at all. Server-rendered content is treated identically everywhere; client-rendered content is not.

Is React or Next.js bad for SEO?

No. The framework is neutral; the rendering mode is what matters. A Next.js app using static generation or SSR for public pages is excellent for SEO. The same app shipped as pure client-side rendering is not.

Will prerendering services solve this?

They can, as a bridge. They serve a static snapshot to crawlers and the app to users. The cost is a second pipeline that drifts from the real site, and the risk of serving materially different content to crawlers. Prefer native SSG/SSR when you control the codebase.

How do I handle infinite scroll?

Keep the scroll experience and add real paginated URLs (?page=2) that render server-side, linked from the listing. Users get the scroll, crawlers get addresses.

My content is behind a tab. Is that a problem?

Only if it is fetched on click. Content present in the HTML and merely hidden with CSS is generally indexed fine. Content that requires a network request triggered by a click is not.

Technical SEOJavaScript SEORenderingCrawler
AttractiveWebAI

Analyze your website with AttractiveWebAI

Get metadata, structure, technology stack, links, images, and IA preview from any public URL.