SEO foundations

Making single-page apps (SPAs) crawlable for GEO

By Abhijay Tondak, Founder · Updated July 2, 2026 · 6 min read

The short answer

Single-page apps (SPAs) are hard for AI crawlers because they render content client-side and often lack real per-page URLs - so crawlers see an empty shell. To make an SPA citable, add server-side rendering or prerendering so content ships in the initial HTML, ensure every piece of content has a real crawlable URL, and verify the raw HTML contains your content. Without these, an SPA's content is largely invisible to non-JS crawlers.

Key takeaways

  • SPAs render client-side → crawlers often see an empty shell, not your content.
  • Add SSR or prerendering so content is in the initial HTML.
  • Every citable piece of content needs a real, crawlable URL (not just a JS route).
  • Verify by fetching raw HTML - the content must be there without JS.
  • A pure client-rendered SPA is the hardest architecture to earn citations with.

Why SPAs are a GEO problem

A single-page app loads one HTML shell and then uses JavaScript to render everything and handle navigation without full page reloads. Great for UX - hard for crawlers. Non-JS crawlers fetch the shell and see almost nothing, because the real content only appears after JS runs. And SPA 'routes' are often client-side only, so there may be no real URL for a crawler to fetch per piece of content. Both problems make SPA content hard to cite.

Fix 1: render content server-side

The core fix is the same as any JS-rendering problem: get your content into the initial HTML via SSR or prerendering. Frameworks (Next.js, Nuxt, etc.) or prerendering services can render SPA content to static HTML so crawlers receive complete pages. This is the single biggest lever for making an SPA citable.

Fix 2: real URLs for real content

Crawlers cite URLs, so every distinct piece of citable content needs its own real, fetchable URL - not just a client-side route that only exists after JS runs. Ensure your routing produces genuine URLs that return the right content when fetched directly, and that they're in your sitemap. Content with no crawlable URL can't be a citation target.

  • Each content page = a real URL that returns that content when fetched directly.
  • Include those URLs in your sitemap.
  • Avoid hash-based (#) routes for citable content - prefer real paths.

Verify like a crawler

After the fixes, verify: fetch each key URL's raw HTML (view source / curl) and confirm your content is present without JS. If a page still returns an empty shell when fetched directly, crawlers still can't see it. Test the actual URLs a crawler would hit, not just the in-app experience. A pure client-rendered SPA with no SSR is the hardest possible architecture for GEO - fixing rendering is usually the highest-impact technical work you can do.

Frequently asked questions

Why can't AI crawlers read my SPA?

SPAs render content client-side after JS runs and often lack real per-page URLs, so non-JS crawlers fetch the shell and see an empty page with no content to cite. It's the hardest architecture for GEO.

How do I make an SPA crawlable?

Add SSR or prerendering so content ships in the initial HTML, and give every citable piece of content a real crawlable URL (not just a client-side route) that's in your sitemap. Then verify the raw HTML contains your content.

Do SPA routes count as real URLs?

Only if they return the right content when fetched directly (server-rendered/prerendered). Client-side-only routes that need JS to exist aren't crawlable URLs. Prefer real paths over hash (#) routes for citable content.

How do I verify my SPA is now crawlable?

Fetch each key URL's raw HTML (view source or curl) and confirm your content is present without JS. If a page returns an empty shell when fetched directly, crawlers still can't see it.

Put this into practice — free.

Get your free AI-visibility audit and see where engines find you today.

Free audit · public pages only · no credit card

More from this topic

Keep building your expertise with related GEO content in the same cluster.

Keep reading