seojuice

SPA SEO in 2026: An HTML-First Field Guide for Next.js, Nuxt, SvelteKit, and Astro

Vadim Kravcenko
Vadim Kravcenko
Oct 25, 2024 · 12 min read

TL;DR: Single-page applications can rank, but only if URLs, status codes, metadata, and primary content exist in the first HTML response. Google may render JavaScript late; most AI crawlers do not render it at all. Pick a rendering model per route, ship documents first and app states second, then let JavaScript hydrate on top. Below: a 2026 field guide for Next.js, Nuxt, SvelteKit, and Astro teams.

Stop asking whether Google "supports JavaScript"

The opening question for SPA SEO is no longer "can Googlebot render JavaScript." Yes, it can. Martin Splitt has been saying so for years. People still debug single-page apps by staring at view-source: as if that were the page Google indexed.

"A lot of people are still looking at view source. That is not what we use for indexing. We use the rendered HTML."

Martin Splitt, Developer Advocate at Google

That kills one bad habit. If you only inspect the initial source of a React, Vue, Angular, SvelteKit, Nuxt, Remix, or Next.js app, you miss what Google eventually sees. Rendered DOM matters.

But the question for 2026 is not whether Google can render. It is whether the document arrives soon enough to be useful for Googlebot, social parsers, and the AI crawlers that increasingly dominate the citation graph.

The AI-crawler gap changed the math

For years, I treated this as a Google-only problem (I was wrong about that for years). Then AI crawlers showed up with a different posture.

"The results consistently show that none of the major AI crawlers currently render JavaScript."

Vercel Engineering

GPTBot, ClaudeBot, PerplexityBot, the Gemini training fetch — all of them fetch raw HTML. If your most important content lives behind hydration, those crawlers see an empty app shell where your pricing table, product copy, FAQ, or comparison content should be. Run our AI Crawler Inspector against a few SPA pages and you will see what they see.

Timeline comparing how Googlebot, non-JavaScript crawlers, and AI crawlers see SPA content
Source: SEOJuice SPA-SEO reference, based on Google rendering documentation and Vercel's measurement of AI-crawler JavaScript execution.

Classify routes before you classify the rendering problem

Most teams skip this step and ask whether the whole SPA needs SSR. That framing wastes engineering time.

A real SPA mixes two things: crawlable pages and private app states. Pricing pages, blog posts, documentation, templates, integrations, comparison pages, and product landing pages are pages. Dashboards, onboarding flows, modals, filters, account screens, and saved reports are app states.

Start with classification. Do not ask engineers to "fix SEO for the app." Ask them to mark which routes deserve search traffic, then choose rendering, indexing, canonicals, and status codes per route.

Route type Should rank? Rendering choice Indexing rule
Blog post Yes SSG or SSR Index, canonical self
Product landing page Yes SSG or SSR Index, canonical self
Internal search results Usually no CSR or SSR Often noindex
Dashboard route No CSR is fine Block behind auth or noindex
Faceted filter URL Sometimes SSR only if curated Canonical or noindex
Decision tree for choosing SEO treatment and rendering model for SPA routes
Source: SEOJuice SPA-SEO route-classification framework.

A SPA with 40 public URLs and 4,000 dashboard states does not have 4,040 SEO pages. It has 40 pages and a product interface. Public routes need stable URLs, self-canonicals, server-delivered metadata, crawlable links, useful first-byte HTML, and correct status codes. Dashboard routes need fast interaction, auth, and state management. Forcing both groups into one rendering model usually makes both worse.

Pick the rendering model per route, not per app

Rendering strategy is an architecture choice, not a plugin setting. If you pick the wrong model, every later ticket becomes a workaround.

Comparison chart of SPA rendering models for SEO
Source: SEOJuice rendering-strategy reference, drawing on Google's rendering documentation and Vercel's Next.js rendering guide.

CSR: fine for dashboards, risky for landing pages

Client-side rendering is perfectly fine for authenticated software screens. If a user must log in, crawlers should not index the route anyway. CSR becomes risky when the same app shell serves pricing pages, docs, articles, and product pages where content appears only after JavaScript runs and APIs respond.

SSG: boring, fast, and usually the right answer

Static site generation builds pages into HTML ahead of time. For blogs, docs, changelogs, glossary pages, templates, and most marketing content, SSG is hard to beat. Fast, cacheable, cheap, crawler-friendly. Astro 5 leans hardest in this direction with its islands architecture and zero-JS-by-default posture.

SSR: useful when public content changes often

Server-side rendering fits when public content varies by request, geography, inventory, permissions, or freshness. Lee Robinson described the basic Next.js model plainly:

"Next.js pre-renders the page into HTML on the server on every request."

Lee Robinson, VP of Developer Experience at Vercel

SSR gives crawlers HTML without waiting for the client bundle while still letting the page reflect fresh data.

ISR and PPR: the practical middle for large sites

Incremental static regeneration refreshes static pages after publish. For programmatic SEO, docs, and large template libraries, ISR can prevent rebuild pain without falling back to full CSR. Next.js 15 promoted Partial Prerendering to a stable config option, so you can ship a static shell and stream the dynamic holes from the same route. That mixes the two postures inside one URL, which is closer to how real product pages actually behave.

Dynamic rendering: the workaround that should expire

Dynamic rendering serves one version to crawlers and another to users. It can rescue a legacy SPA when a migration is not ready, but I would not design a new search strategy around it.

"So I would see this as something kind of as a temporary workaround – where temporary might mean a couple of years – but it's more of a time-limited workaround."

John Mueller, Search Advocate at Google

Use dynamic rendering as a bridge. Replace the bridge with server-rendered or static-first public routes.

Framework specifics that matter in 2026

The right rendering model is also a framework conversation. Defaults moved in the last twelve months, and a few of those moves change SPA SEO decisions.

  • Next.js 15. React 19 stable, Turbopack dev stable, fetch and GET route handlers no longer cached by default. The default-cached behavior in earlier versions hid stale-content bugs; the new defaults force you to make caching explicit per route. Partial Prerendering (PPR) is the headline SEO feature — a static shell with streamed dynamic holes — which lets product pages stay crawler-friendly without giving up freshness. For a deeper walkthrough see our Next.js, React, and Nuxt SEO guide.
  • Nuxt 4. Application code now lives in app/ by default. Separate TypeScript projects for app, server, shared, and builder code reduce the type-bleed problems that used to leak server-only secrets into client bundles. Nuxt 4.4 (March 2026) added vue-router v5 and typed layout props, both small wins for shipping route-specific metadata cleanly.
  • SvelteKit 2 with Svelte 5 runes. Pages are server-rendered by default; prerendering is opt-in per route. The mental model is closer to "every route is SSR unless I say static," which inverts the SPA-by-default trap. Bundle sizes stay small enough that hydration cost rarely blocks first paint.
  • Astro 5 (and 6). Zero JavaScript by default, islands architecture, multi-framework component support. After Cloudflare acquired Astro in January 2026, the edge deployment story tightened further. Astro is the easy answer for content-heavy SPAs that need React or Vue components on specific pages only.

The framework matters less than the per-route discipline. Any of these four can ship a crawlable SPA. All four can ship an invisible one if you put public content behind hydration.

The crawl traps that still break indexing

The hard SPA SEO failures are usually boring. Not mysterious ranking penalties — delivery bugs.

The first trap is the universal shell. Every URL returns the same 200 response, the same empty root, the same bundle. The router decides later whether /pricing, /docs/api, or /totally-fake-url exists. That makes crawlers work too hard, and it creates the second trap: soft 404s.

"Instead of responding with 404, it just responds with 200 … always showing a page based on the JavaScript execution."

Martin Splitt, Developer Advocate at Google

Invalid routes should return real 404 or 410 status codes. A cute client-side "page not found" component served with 200 still wastes crawl budget and confuses indexing signals.

Diagram showing the difference between SPA soft 404 responses and real 404 status codes
Source: SEOJuice SPA-SEO crawl-trap reference, based on Google's soft-404 documentation and Martin Splitt's public guidance.

The third trap is navigation crawlers cannot follow. Buttons, click handlers, and router events are fine for interaction, but internal discovery still needs anchors with real href values. If your most important pages are reachable only after a JavaScript handler runs, your crawlability is weaker than it looks.

Metadata is another common failure. Many SPAs update titles, descriptions, canonicals, robots tags, Open Graph, and schema after route changes. That works visually in a browser tab. It still fails for crawlers, social parsers, and AI bots. Route-specific metadata should be in the returned HTML.

Canonicals deserve their own warning. I have seen hydrated apps overwrite a correct canonical with a staging domain, a root URL, or the previous route. The bug is quiet until duplicate URLs cluster badly or the wrong page starts ranking.

Infinite scroll hides content behind client state. If page two, three, and older items have no crawlable URLs, search engines may never discover them. Use paginated fallback URLs for important archives and category pages.

API-loaded main content is fragile. If the H1, body copy, product details, reviews, or internal links require API calls after hydration, you have more failure points. Bot traffic hits rate limits. APIs block unfamiliar user agents. Timeouts leave the rendered DOM thin.

What AI Overviews actually cite from a SPA

This is the 2026 wrinkle. Google's AI Overviews summarize answers above the organic list and cite the sources they used to compose the summary. Those citations are the new top of the funnel, and they reward a specific HTML shape.

The citation system pulls from clear, static signals: visible text, headings, lists, tables, semantic HTML available at first response. It does not patiently wait for hydration. If your most quotable paragraph appears only after a useEffect call, an AI Overview will not see it.

Three things matter for SPA pages that want to be cited:

  1. HTML-first answer paragraphs. The first 300-400 words of any indexable route should answer the route's primary question without JavaScript. Most AIO citations live in those first sections.
  2. Semantic structure for lists and tables. Bulleted lists, ordered lists, and HTML tables get extracted directly into Overviews. Divs-pretending-to-be-tables do not.
  3. Stable URLs the AI bot can refetch. AI Overviews recheck cited sources. Hash-routed pages, query-only routes, and dynamic-rendering forks make refetch flaky.

The same logic applies to Perplexity citations, ChatGPT browsing answers, and Claude's web reads. Their crawlers fetch raw HTML; if the answer is not there, the page is not cited. Optimizing for AI Overview citations goes deeper, and our AIO impact analysis explains why the impression bleed feels worse than the click loss.

Ship every indexable route as a document first

The rule I keep coming back to: if the route deserves search traffic, the first response should look like a page.

That means each public URL returns useful HTML with the core signals already in place:

  • Correct <title>.
  • Meta description.
  • Self-referencing canonical.
  • One clear H1.
  • Main content.
  • Crawlable internal links.
  • Structured data where relevant.
  • Correct status code.
  • Open Graph and Twitter tags if sharing matters.
HTML-first SPA page structure with JavaScript hydration added after core SEO elements
Source: SEOJuice SPA-SEO architecture playbook for HTML-first public routes.

JavaScript can then hydrate components, personalize elements, track events, and enrich the experience. It should not be required for the crawler to understand what the page is about.

Site architecture matters too. A public route with no crawlable links pointing to it is still weak — even when it is server-rendered. A perfectly rendered document buried five clicks deep behind client-only navigation does not perform like one inside a clear internal linking system.

Test SPA SEO with rendered HTML, not hope

If you only test the browser experience, you test the happiest path. SPA SEO needs uglier tests.

  1. Fetch the URL with JavaScript disabled and check whether the content still makes sense.
  2. Inspect the URL in Google Search Console and review the rendered HTML.
  3. Compare initial HTML against the rendered DOM in Chrome DevTools.
  4. Test status codes directly with curl -I https://example.com/missing-route.
  5. Crawl the site with one JS-capable crawler and one non-JS crawler.
  6. Confirm titles, canonicals, robots tags, schema, and internal links exist before hydration.
  7. Check server logs for bot hits, blocked APIs, timeouts, and unexpected redirects.
  8. Validate structured data with Google's Rich Results Test after rendering.

The uncomfortable test is the H1 test. If Googlebot needs five steps and two API calls to find the H1, the page is fragile even if it eventually gets indexed. Apply the same test to GPTBot, ClaudeBot, and PerplexityBot: if those crawlers cannot see the H1 in raw HTML, the route will not show up as an AI citation.

SPA SEO checklist for 2026

Use this at route level. A sitewide "pass" hides too many SPA failures.

  • Rendering: Public pages use SSG, SSR, ISR, or PPR. Private app screens can stay on CSR.
  • Routing: Every indexable URL has a unique route, unique content, and a self-canonical.
  • Status codes: Missing pages return 404 or 410, not 200.
  • Links: Internal navigation uses crawlable anchors with real href attributes.
  • Metadata: Titles, descriptions, canonicals, robots tags, Open Graph, and schema are route-specific and present in HTML.
  • Content: Main copy, H1s, product information, and key links exist without waiting on client-only data.
  • Performance: Bundle size, hydration cost, third-party scripts, and route-level code splitting are controlled.
  • Index control: Dashboards, private routes, low-value filters, and thin search pages are blocked or noindexed.
  • Testing: Initial HTML, rendered DOM, and indexed content are compared on important templates.
  • AI visibility: Quotable answer paragraphs, lists, and tables appear in raw HTML so AI Overviews and AI search engines can cite them.

Search, AI answers, link previews, and discovery systems all depend on access. Rankings come second.

The simplest SPA SEO architecture I would ship today

If I were starting a modern SPA with search traffic in mind, I would not make the entire product server-rendered. I would split it.

Site area Recommended approach
Marketing site Static generation
Blog and docs Static generation or ISR
Product pages SSR, ISR, or Next.js PPR
Programmatic SEO pages Static generation with strong pruning
Dashboard CSR behind auth
Search and filter pages Noindex unless manually curated
Invalid routes Real 404 or 410
Shared layout Server-rendered metadata and navigation

Marketing pages and articles should be HTML-first. Product surfaces that need freshness use SSR, ISR, or PPR. The dashboard stays app-like because ranking it would be pointless. Programmatic SEO pages need restraint. Static generation makes thousands of pages easy, including thousands nobody should index. Generate only pages with real search demand, useful content, and internal links. Prune the rest before Google has to make the decision for you.

The winning SPA is not the one that proves crawlers can run JavaScript. The winning SPA is the one that does not make crawlers do unnecessary work.

FAQ

Can a single-page application rank on Google in 2026?

Yes. A SPA can rank if indexable routes return crawlable content, correct metadata, internal links, and valid status codes in the first HTML response. Google can render JavaScript, but relying on rendering for everything makes the site more fragile and invisible to AI crawlers that do not render at all.

Is server-side rendering required for SPA SEO?

Not for every route. SSR fits public pages with changing content. SSG or ISR is often better for stable content. Next.js 15's Partial Prerendering blends both in one route. CSR is fine for private dashboards and app states that should not be indexed.

Are hash routes bad for SEO?

Hash routes are a poor choice for indexable pages. They can work for on-page fragments, but public content should have clean URLs, route-specific metadata, and server-level status codes.

Should SPA search results pages be indexed?

Usually no. Internal search pages and faceted filters often create thin or duplicate URLs. Curated filter pages can be indexed when they have unique demand, stable content, and a clear canonical strategy.

How do I know if my SPA has a soft 404 problem?

Request a fake URL and check the status code. If /this-page-should-not-exist returns 200 with a client-side not-found message, you have a soft 404 risk that wastes crawl budget.

Will AI Overviews cite a JavaScript-rendered SPA?

Only if the cited content exists in raw HTML. Most AI crawlers do not execute JavaScript. Server-render or pre-render the answer paragraphs, lists, and tables you want quoted.

Need help turning your SPA into crawlable pages?

SEOJuice helps teams strengthen crawlable internal links and HTML-first signals across the public pages that deserve search traffic. If your SPA has orphaned routes, buried templates, or pages Google never seems to reach, automated internal linking can make the document layer easier for crawlers and AI bots to follow.

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "Can a single-page application rank on Google in 2026?", "acceptedAnswer": { "@type": "Answer", "text": "Yes. A SPA can rank if indexable routes return crawlable content, correct metadata, internal links, and valid status codes in the first HTML response. Google can render JavaScript, but relying on rendering for everything makes the site more fragile and invisible to AI crawlers that do not render at all." } }, { "@type": "Question", "name": "Is server-side rendering required for SPA SEO?", "acceptedAnswer": { "@type": "Answer", "text": "Not for every route. SSR fits public pages with changing content. SSG or ISR is often better for stable content. Next.js 15's Partial Prerendering blends both in one route. CSR is fine for private dashboards and app states that should not be indexed." } }, { "@type": "Question", "name": "Are hash routes bad for SEO?", "acceptedAnswer": { "@type": "Answer", "text": "Hash routes are a poor choice for indexable pages. They can work for on-page fragments, but public content should have clean URLs, route-specific metadata, and server-level status codes." } }, { "@type": "Question", "name": "Should SPA search results pages be indexed?", "acceptedAnswer": { "@type": "Answer", "text": "Usually no. Internal search pages and faceted filters often create thin or duplicate URLs. Curated filter pages can be indexed when they have unique demand, stable content, and a clear canonical strategy." } }, { "@type": "Question", "name": "How do I know if my SPA has a soft 404 problem?", "acceptedAnswer": { "@type": "Answer", "text": "Request a fake URL and check the status code. If /this-page-should-not-exist returns 200 with a client-side not-found message, you have a soft 404 risk that wastes crawl budget." } }, { "@type": "Question", "name": "Will AI Overviews cite a JavaScript-rendered SPA?", "acceptedAnswer": { "@type": "Answer", "text": "Only if the cited content exists in raw HTML. Most AI crawlers do not execute JavaScript. Server-render or pre-render the answer paragraphs, lists, and tables you want quoted." } } ] } </script>

Discussion (2 comments)

Business Builder

Business Builder

7 months, 1 week

Love this deep dive on SPAs and client-side rendering — the React/Vue/Angular SEO pitfalls were explained really well! 🙌 I migrated a React SPA to hybrid SSR + prerendering and saw a crawl/index uptick in under a week. Please do a tutorial on hydration and sitemap strategies next 🙏

KeywordMaster

KeywordMaster

7 months, 1 week

Nice — glad it helped and awesome you saw a quick uptick! I did the same move from CRA SPA → hybrid SSR + prerendering last year and saw similar gains, fwiw.

A few practical tips from that migration that might help for the tutorial you asked for:
- Hydration pitfalls: mismatches usually come from non-deterministic things in render (Date.now(), Math.random(), generated IDs, or useEffect producing visible DOM changes). Fix by moving client-only stuff into useEffect or guarding it (if (typeof window === 'undefined') ...), or use deterministic id libs.
- Streaming/partial hydration: if you’re using React 18/Next, streaming SSR + selective client hydration (islands-ish or client boundary components) reduces TTI without sacrificing SEO — imo worth covering.
- Debugging: curl or fetch the page server-side and compare to what Chrome renders after hydration; React devtools console will show hydration mismatch warnings. Also check Search Console’s “Inspect URL” to see what Googlebot sees.
- Sitemap strategy: generate sitemaps at build for static routes, dynamically for API-driven content (rebuild or incremental), split into sitemap index if >50k URLs, include lastmod, and reference it from robots.txt. For multi-lingual sites include hreflang entries or separate sitemaps per locale.
- Tools I used: Next.js (SSR + static props), next-sitemap for generation, prerender.io for tricky bots, and Search Console + server logs to confirm indexing.

If you want, I can write that hydration + sitemap tutorial — what would you prefer: code-heavy step-by-step for Next.js, or framework-agnostic notes + examples? Any stack specifics (Next/Remix/Vite/Netlify) you’re on?

GrowthHacker23

GrowthHacker23

7 months, 1 week

ngl SPAs can rank.