JavaScript & Framework SEO

React SEO Without SSR: Prerendering, Static Export, and Limits

Published: April 3, 2026 11 min read

Yes, a React app can rank on Google without server-side rendering. Google crawls, renders, and indexes client-side JavaScript, so a plain CRA or Vite SPA is not doomed. But that yes comes with real limits: your pages wait in a rendering queue, AI crawlers and social scrapers never execute your JavaScript, and every rendering-dependent step is a place where indexing can silently fail. React SEO without SSR means picking one of three workable paths around those limits, not pretending they don’t exist.

Most React SEO guides end with “migrate to Next.js.” This one is for when that isn’t an option.

Can a React app rank without SSR? (short answer: yes, with real limits)

A client-rendered React app can appear in Google’s index and rank for real queries. What it can’t do is compete on equal footing with served HTML: indexing is slower, more fragile, and invisible to anything that doesn’t run a headless browser. The practical question is not “can it rank” but “how do I get indexable HTML for the routes that matter without running a Node server.”

You have three real options:

  1. Prerender at build time. Generate a static HTML file per route during your build and ship it to a CDN. No server, no runtime, crawlers get finished HTML.
  2. Use a runtime prerendering service. Middleware detects bots and serves them headless-Chrome-rendered HTML while users get the normal SPA. The classic prerender.io pattern.
  3. Move the SEO pages out of React entirely. Keep the app a SPA and build the pages that need to rank on a static-first stack, on the same domain.

If you want the full landscape including the SSR routes, our complete React SEO guide covers it. This article assumes SSR is off the table and works within that.

Why “just use SSR” is sometimes off the table

Telling someone with a five-year-old client-rendered codebase to “just add SSR” is technically a solution and practically a different project. The constraint is usually one of these:

All four are legitimate, and they share an implication: you almost never need to fix rendering for the whole app, just for a small set of URLs. Keep that scope in mind through everything below.

What Google actually does with client-rendered React

Google processes JavaScript apps in three phases: crawling, rendering, and indexing. That’s straight from Google’s JavaScript SEO documentation, which also explains the mechanics: rendering runs on an evergreen version of Chromium, and after crawling, a page sits in a rendering queue “for a few seconds, but it can take longer than that.” Until that render happens, Google is working with your empty HTML shell.

The same documentation contains a nasty gotcha: when Google encounters a noindex robots tag in the initial HTML, it may skip rendering and JavaScript execution entirely. Shipping a noindex in the shell and removing it client-side doesn’t work; the tag Google sees before rendering is the one that counts.

Does Google penalize client-side rendering? No. A well-built client-rendered app with clean URLs and correct status codes will get indexed. What you pay is latency and fragility: a JS error, a timed-out API call, or a blocked resource during Google’s render, and the indexed page is blank. If your SPA also has routing or soft-404 problems, fix those first. Our SPA SEO guide covers that layer.

Option 1: Prerender at build time (static export)

Build-time prerendering runs your React app once, at build, and writes rendered HTML for each route to disk. Crawlers, scrapers, and AI bots all get finished HTML with zero servers involved.

Is prerendering the same as SSR? No. SSR renders each page per request on a live server; prerendering renders each page once at build time, and after that it’s just files. For content that doesn’t change between deploys, the crawler-facing result is identical.

Your tooling options, in order of how likely they are to fit an existing app:

// react-router.config.ts
import type { Config } from "@react-router/dev/config";

export default {
  ssr: false,
  async prerender() {
    return ["/", "/pricing", "/features", "/about-us"];
  },
} satisfies Config;
Ranked list of build-time prerender tools by fit, best first: React Router v7 prerender is the shortest path if you already use React Router on Vite, Next.js static export is rarely the cheapest route just for the exporter, Vite prerender plugins vary in quality so test the output, and react-snap is unmaintained.
Reach for the tool nearest the top of your existing stack, and skip react-snap on anything new.

Prerender every URL you want indexed: home, pricing, features, blog, docs, landing pages. The app behind the login stays client-rendered, since nothing there belongs in the index anyway.

Option 2: Runtime prerendering services (prerender.io and friends)

The dynamic rendering pattern: middleware in front of your app inspects the user agent. Humans get the normal SPA. Bots get a cached, headless-Chrome-rendered HTML snapshot from a service like prerender.io. Nothing about your build changes, which is the entire appeal.

Google’s position deserves quoting precisely, because half the articles recommending dynamic rendering skip it. Google states that dynamic rendering “is a workaround and not a recommended solution,” and recommends server-side rendering, static rendering, or hydration instead, as stated in Google’s dynamic rendering documentation. The same page answers the cloaking question: “Googlebot generally doesn’t consider dynamic rendering as cloaking,” as long as it produces similar content to what users see. Serving bots a prerendered version of the same page is fine; serving them different content is not.

Runtime prerendering is still the pragmatic call for a huge legacy SPA with thousands of long-tail URLs and no rebuild budget. Rebuilding ten thousand programmatic pages as static exports is a project; pointing a prerender service at them is config.

Price it honestly, though:

Pros and cons of runtime prerendering services. Pros: nothing about your build changes, and it is the pragmatic call for a huge legacy SPA with thousands of long-tail URLs and no rebuild budget. Cons: cache staleness lags fast-changing content, a vendor sits in your critical path so its outage becomes your indexing outage, and two versions of every page invite silent drift.
A runtime service buys indexable HTML today, but puts a vendor and a second copy of every page in your critical path.

Treat it as a bridge: a way to stop the bleeding while you move the URLs that matter onto option 1 or option 3.

Option 3: Hybrid islands, or taking the SEO pages out of React entirely

Here’s the reframe most teams miss: you don’t need SSR for the app. You need indexable HTML for the twenty-odd URLs that drive leads. Nothing says those URLs have to be rendered by the same stack as your dashboard.

The hybrid island setup: keep the React app exactly as it is, and build the pages that need to rank (marketing pages, blog, docs, landing pages) on a static-first stack, served from the same domain. Routing happens at the CDN or proxy level: /blog/* and /guides/* go to the static build, everything else falls through to the SPA shell.

Two decisions inside this option:

It also decouples failure modes: a broken app release can no longer blank your blog out of Google’s index, because the two no longer share a deploy pipeline.

Meta tags without SSR: React 19 metadata vs react-helmet, and their shared blind spot

For years the standard answer to per-route titles in a SPA was react-helmet, then react-helmet-async after the original went unmaintained. React 19 closed that chapter: per the React v19 release notes, the stable release (December 5, 2024) natively supports rendering <title>, <meta>, and <link> tags inside any component and hoists them to <head> automatically, in client-only apps, streaming SSR, and Server Components alike. On React 19, you render the tags in the route component and you’re done; no helmet library needed.

Now the part every helmet tutorial skips: none of this changes who can see the tags. Client-injected metadata exists only after JavaScript runs. Google’s renderer picks it up eventually; social scrapers from X, Slack, LinkedIn, and WhatsApp don’t run JavaScript, so they read the raw HTML and show the same default title and image for every URL you share.

React 19 metadata, react-helmet, react-helmet-async: all three share this blind spot, because it isn’t a library problem. The only fix for React SEO meta tags without SSR is making the tags exist in the served HTML, exactly what the three options above provide. Metadata is a reason to prerender, not a substitute for it.

The honest limits: what you cannot fix without server-rendered HTML

Prerendering closes most of the gap for stable pages. Here is what stays open; know it before choosing an architecture, not after.

AI search doesn’t render JavaScript at all. Vercel’s analysis of AI crawler traffic, published in The rise of the AI crawler, found that none of the major AI crawlers currently render JavaScript. GPTBot made 569 million fetches in a month and Claude 370 million, and while they download JavaScript files (11.5% of GPTBot requests, 23.84% of Claude’s), they never execute them. A client-rendered React page contributes nothing to ChatGPT or Claude answers about your category; we tested this bot-by-bot in which AI crawlers execute JavaScript. Prerendered routes are fine, because static HTML serves everyone.

Freshness requires rebuilds. Build-time prerendering snapshots your content at deploy. There is no ISR-style per-request regeneration on a static host, so fast-changing public content either goes stale or forces frequent builds.

Genuinely dynamic pages can’t be prebuilt. Search results, personalized views, live inventory: anything assembled per request needs a server. If those pages are your SEO surface, SSR is the answer; plan the migration rather than working around it.

Client-side error states become soft 404s. A SPA that renders “not found” in JavaScript while returning HTTP 200 teaches Google to index garbage. Static files get this right for free; anything still client-rendered needs deliberate handling.

If your lead-generation pages sit on the wrong side of these limits, that’s not a technical footnote. It’s your pipeline quietly excluded from an entire channel.

Which path fits your site? (decision table + next step)

Your situationBest path
Marketing or content site that happens to be built in ReactBuild-time prerender / static export, for all indexable routes
App with a small set of money pages (pricing, landing, blog)Hybrid island: static stack for those URLs, SPA untouched
Large legacy SPA, thousands of long-tail URLs, no rebuild budgetRuntime prerendering service, as a bridge
Dynamic, personalized, or fast-changing pages on competitive SERPsSSR is the real answer; plan the migration

Before committing to any of these, find out what Google currently sees: run your key URLs through URL Inspection in Search Console and compare the rendered HTML against what you ship. Teams are routinely surprised both ways: pages they assumed were invisible render fine, and pages they counted on sit unindexed behind a failed render.

That diagnosis is the first thing we produce in a JavaScript SEO audit: a URL-by-URL account of what Google renders, what it indexes, and what AI crawlers can see, so you pick an architecture on evidence instead of forum threads. Whichever path you take, take it for the URLs that pay for themselves, and leave the rest of the app alone.

Probably, we have already answered your question here

Can a React app rank on Google without SSR?

01

Yes. Google crawls, renders on an evergreen Chromium, and indexes client-side JavaScript, so a plain CRA or Vite SPA is not penalized and can rank for real queries. The catch is that indexing is slower and more fragile: pages wait in a rendering queue, and one JS error or blocked resource during Google's render leaves the indexed page blank. For anything beyond Google, prerender the URLs that matter, because AI crawlers and social scrapers never run your JavaScript.

Is prerendering the same as SSR?

02

No. SSR renders each page per request on a live server, while prerendering renders each page once at build time and ships the resulting HTML as static files. For content that does not change between deploys the crawler-facing result is identical, but prerendering needs no Node runtime, which is the whole point when your hosting is static by design. The tradeoff is freshness: build-time HTML only updates when you rebuild.

Should I use prerender.io or static export for React SEO?

03

Static export (build-time prerender) is the better default because it produces real HTML with no vendor in your indexing path and no two-versions-of-a-page drift. Runtime services like prerender.io make sense as a bridge for a large legacy SPA with thousands of long-tail URLs and no rebuild budget, since pointing a service at them is config rather than a project. Google itself calls dynamic rendering "a workaround and not a recommended solution," so treat it as temporary, not the destination.

Why do my React pages show the same title and image when shared on social?

04

Client-injected metadata, whether from React 19's native tags, react-helmet, or react-helmet-async, only exists after JavaScript runs. Scrapers from X, Slack, LinkedIn, and WhatsApp do not execute JavaScript, so they read your raw HTML shell and show the default title and image for every URL. The only fix is putting the tags in the served HTML, which is what build-time prerendering or a static stack for your marketing pages gives you.

Do AI crawlers like ChatGPT and Claude read client-rendered React?

05

No. Per Vercel's analysis of AI crawler traffic, none of the major AI crawlers currently execute JavaScript; GPTBot and Claude download JS files but never run them. A client-rendered React page therefore contributes nothing to AI answers about your category, while a prerendered or statically served route is fully readable. If AI search visibility matters to your pipeline, indexable HTML is the price of entry. Our JavaScript SEO audit maps URL by URL what Google renders and what AI crawlers can see.