Single page application SEO
Last reviewed
A single page application builds its pages in the browser with JavaScript, so the HTML your server sends is close to empty. Google renders JavaScript and can index a well-built SPA, but the major AI crawlers do not run JavaScript, so they need finished HTML from server rendering, prerendering or a prerender service.
What GPTBot sees
<body> <div id="root"></div> <script src="/app.js"> </body>0 words
With Prebaked
Example page. Bots and people get the same content: the page your visitors' browsers already build.
SPA SEO in five rules
| Rule | Why | Source |
|---|---|---|
| Send content in the HTML, not only through JavaScript | The major AI crawlers do not render JavaScript | Vercel and MERJ, Dec 2024 |
Use real URLs with the History API, not #/ fragments |
Google recommends the History API for SPA routes | Google Search Central |
Use <a href="..."> for links |
Google "can only crawl your link if it's an <a> HTML element" with an href |
Google Search Central |
Return a real 404, or add noindex, for missing pages |
A 200 error page "can lead to error pages being indexed" | Google Search Central |
| Give every route its own title, description and canonical in the HTML | Without it, crawlers that do not run JavaScript get the same title from index.html on every route |
See the check below |
The problem: two kinds of crawlers
Google renders, with a delay. Google processes JavaScript apps in three phases: crawling, rendering and indexing. Pages with a 200 status go into a render queue, and "once Google's resources allow, a headless Chromium renders the page". Vercel and MERJ measured the delay on nextjs.org in April 2024: median 10 seconds, 90th percentile about 3 hours, 99th percentile about 18 hours.
AI crawlers do not render at all. Vercel and MERJ found that "none of the major AI crawlers currently render JavaScript". This covers OpenAI (GPTBot, OAI-SearchBot, ChatGPT-User), Anthropic (ClaudeBot), Meta, ByteDance and Perplexity. They read what the server sends, and for an SPA that is an empty root element. Gemini and AppleBot are exceptions that render.
This matters more each year. Cloudflare reported that GPTBot requests rose 305% and ChatGPT-User requests 2,825% from May 2024 to May 2025.
The result: an SPA can rank on Google and be invisible to ChatGPT, Claude and Perplexity. Your site looks fine in every browser, so you do not notice. Read more on how AI crawlers see your site and whether ChatGPT can read JavaScript.
How to check your SPA
- Free checker. Enter a URL to compare the page without JavaScript and in a real browser.
See what ChatGPT sees on your site
- Raw HTML. Fetch a deep URL, not only the home page.
curl -s https://www.<your-domain>/pricing | grep -o "<title>.*</title>"
curl -s -o /dev/null -w "%{http_code}\n" https://www.<your-domain>/this-page-does-not-exist
The first line shows the title crawlers get without JavaScript. If every route shows the same title, it comes from your static index.html. The second line should print 404. Many SPAs print 200 because the server returns index.html for every path.
- Google. Use the URL Inspection tool or the Rich Results Test to see the HTML Google rendered.
Your options
Server-side rendering or static generation
Move the app to a framework that renders HTML on the server or at build time. For React, the React team recommends Next.js or React Router. Vue, Angular and Svelte have their own options.
Best when you are starting out or rebuilding anyway. It is the most complete fix, and Google recommends "server-side rendering, static rendering, or hydration" as the long-term solution. It costs a migration and, for server rendering, a host that runs server code.
Build-time prerendering
A tool crawls your built app and saves one HTML file per route. Best for small sites with few routes that change only on deploy. Routes created from live data are missing until the next build. See the React SEO guide for react-snap.
A prerender service
A service renders your pages in a real browser when a crawler asks and serves the HTML from a cache. People still get the SPA. Google calls this dynamic rendering, a "workaround and not a long-term solution", and says that with similar content it is not cloaking.
Best when you have a working SPA, many routes or frequently changing content, and no plan to migrate soon.
Meta tag libraries alone
React Helmet, vue-meta and similar libraries set titles and descriptions from JavaScript. They help Google after rendering, but they do not put content or tags in the HTML that AI crawlers receive. Use them together with one of the options above.
When Prebaked is the right choice
Prebaked is a prerender service for SPAs that already work for people and should also work for crawlers.
- No rewrite: add a snippet for nginx, Netlify, Cloudflare or another platform.
- AI crawlers included: the bot list covers GPTBot, OAI-SearchBot and ClaudeBot as well as search engines.
- Same content for everyone: Prebaked renders your page as it is. No content is added or changed for bots.
- Correct status codes: a page that renders as a 404 returns 404 to crawlers, and pages can set their status with a
prerender-status-codemeta tag. - Never worse than today: if Prebaked is slow or unavailable, crawlers get your original page.
- Cache hits are free. Warm the cache from your sitemap so crawlers get cached pages.
Prebaked does not fix thin content, duplicate titles or broken links. It makes sure crawlers receive the page your visitors see.
FAQ
Are single page applications bad for SEO?
Not for Google, if routes, links and status codes are set up correctly, because Google renders JavaScript. They are a problem for AI crawlers, which do not run JavaScript and see an empty page unless the server sends finished HTML.
Does Google index single page applications?
Yes. Google queues SPA pages for rendering and indexes the rendered HTML. Rendering can take from seconds to hours, and pages need real URLs, <a href> links and correct status codes.
How do I make my SPA visible to ChatGPT?
Send finished HTML to its crawlers. You can render on the server, prerender at build time or use a prerender service. Then check the result with the free checker.
Is prerendering an SPA cloaking?
Not when crawlers get the same content as people. Google's dynamic rendering guidance says Googlebot does not treat it as cloaking as long as it "produces similar content".
Should I use hash URLs in my SPA?
No. Google recommends the History API for loading different content per URL in an SPA, so each view has its own crawlable address.