Yes, a React website can rank on Google, but how its pages are rendered decides the outcome. With pure client-side rendering (CSR), Googlebot first receives an almost empty HTML document and processes the content later in a deferred rendering queue. Serving pages as ready-made HTML through SSR, SSG or prerendering removes most of that indexing risk.
Why do React SPAs have indexing problems?
React SPAs, or single-page React applications, usually send the browser nothing more than an empty root div and a JavaScript bundle. Headings, body text and links only appear after that JavaScript runs. When a crawler reads the raw HTML of a React SPA, there is no content yet, which delays or weakens indexing.
The problem is not limited to Google. Bing processes JavaScript less thoroughly, a large share of AI answer-engine crawlers such as those behind ChatGPT and Perplexity fetch raw HTML only, and link-preview bots from WhatsApp, LinkedIn and X never execute scripts. To all of them, a pure CSR React site is a blank page without a title or description. That makes it an AEO (answer engine optimization) problem as much as an SEO one.
- —Pages sitting in 'Crawled - currently not indexed' in Search Console for weeks.
- —Google showing the domain name, or the same title for every page, instead of the real page title.
- —Shared links appearing without a preview image or description.
- —Newly published pages taking weeks to enter the index.
Related services
How does Googlebot render JavaScript?
Googlebot renders JavaScript with an evergreen, up-to-date version of Chromium, but rendering does not happen at crawl time; it happens later in a deferred queue. Googlebot first crawls the raw HTML and discovers the links in it, then schedules the page for rendering when resources allow. For most pages the delay is short, but it is never guaranteed.
The SEO community long called this 'two-wave indexing'. Google now says the median rendering delay is measured in minutes, yet large or low-authority sites can wait considerably longer. On top of that, API calls that time out during rendering, JavaScript files blocked in robots.txt and content that only loads after a click or scroll may never be seen at all. Navigation built with onClick handlers instead of real anchor elements with href attributes also makes page discovery harder.
What is the difference between CSR, SSR, SSG and prerendering?
The core difference between CSR, SSR, SSG and prerendering is where and when the HTML is produced. With CSR the HTML is built in the browser, with SSR it is built on the server for every request, and with SSG it is built once at build time. Prerendering applies the SSG idea to an existing React SPA at build time.
- —CSR (client-side rendering): the server sends an empty shell and the browser builds the content. Simplest to develop, weakest for SEO and first-load speed.
- —SSR (server-side rendering): the server generates HTML on every request. Ideal for personalised or fast-changing content, but it needs an always-on Node.js server and more infrastructure.
- —SSG (static site generation): every page is generated as static HTML at build time. The fastest and cheapest option to host; content changes require a rebuild.
- —Prerendering: SSG retrofitted onto an existing React SPA. Routes are rendered at build time and hydrated in the browser. It solves the SEO problem without switching frameworks.
- —Hybrid approaches (such as ISR): offered by frameworks like Next.js, these regenerate static pages in the background at set intervals.
For corporate sites, service pages and blogs, where content does not change several times a day, SSG or prerendering is usually the sensible choice. Personalised dashboards and pages showing live stock or prices are better served by SSR, or by CSR backed by an API. Most real projects mix the two: static marketing pages, a client-rendered application area.
How do you prerender a Vite + React site without moving to Next.js?
Prerendering a Vite + React site means building the app for both the browser and Node, then turning every route into HTML with renderToString during the build. Moving to Next.js is not required; Vite's SSR build mode, a small Node script and a list of routes achieve the same result. The 404 Ajans website runs on exactly this setup.
On our own site (React 18 + Vite) the pipeline looks like this:
- —Routes are declared in two entry files: the client entry for the browser (main.jsx) and the server entry for Node (entry-server.jsx). Both lists are kept identical.
- —The server entry exports a function that generates every URL, Turkish pages and their English counterparts under /en, straight from the content data arrays.
- —The build runs in three steps: the client bundle, then a Node-runnable server bundle via vite build --ssr, then the prerender script.
- —For each route, the prerender script renders the app inside a server-side router with renderToString and injects the result into the root div of the index.html template.
- —Per-route title, meta description, canonical, hreflang and JSON-LD tags come from a single data function and are written into the head.
- —Each URL gets its own index.html in the dist folder, and sitemap.xml and llms.txt are generated from the same data array.
- —In the browser, main.jsx calls hydrateRoot when the root already contains server HTML, and createRoot otherwise (in development).
The critical point is that server and browser must produce identical HTML. Using browser-only values such as window, localStorage or the current time during render causes hydration mismatch warnings, so that code belongs inside useEffect. Using the same data builder for head tags on both sides prevents drift. In our experience react-helmet-async did not reliably emit head tags in this setup, so we replaced it with a small custom head manager.
For your project
How do you test that prerendering works?
The quickest way to test that prerendering works is to inspect the page's raw HTML source. If the 'view page source' screen in the browser already shows the heading, body text and links, crawlers that never execute JavaScript can see them too. Then use the URL Inspection tool in Search Console to check the HTML Google actually rendered.
- —Fetch the page with curl and confirm the H1 and body text are present.
- —Check that every page has its own unique title and canonical tag.
- —Run a live test with Search Console URL Inspection.
- —Use the Rich Results Test to confirm JSON-LD is readable in the raw HTML.
- —Look for hydration warnings in the browser console.
- —Verify the server returns the correct fallback for paths that do not exist.
In short, React is not an obstacle to SEO; producing content only in the browser is. For most corporate sites, adding prerendering to an existing Vite project is a far lower-risk move than migrating to a different framework.
