A React app built the default client-side way sends the browser an almost empty HTML file and a bundle of JavaScript. The browser runs the bundle and draws the page. For visitors that is fine. For React SEO, the content does not exist until a crawler has run your code, and familiar React habits (click handlers that navigate, one shared page title, a catch-all route that answers 200 for everything) can hide pages or get error views indexed.
React SEO is the work of making sure a single-page app (SPA) hands crawlers real URLs, real links, the right metadata and honest status codes. This guide is for developers and owners of React sites built with Vite or a similar tool, or with React Router. You will leave with a way to see what a crawler receives, the React-specific failures to look for, working code for per-route titles and links, and a decision between staying client-rendered, prerendering or moving to a framework. For the general crawl-render-index mechanics, read JavaScript SEO: Server Rendering, Client Rendering and Crawlers; this guide stays with React.
Short answer: Google can index content from a client-rendered React app, because it renders JavaScript, but rendering can be delayed and fail. For dependable React SEO, give every view a real URL and an <a href> link, set unique titles per route, return true 404 responses, and consider prerendering or server rendering for pages that matter for search.
Before you begin
- Access: the repository, the ability to build and deploy to a staging copy, and Google Search Console for the live site.
- Know your setup: React version, router (React Router or another), build tool, and whether any server renders HTML. If you are not sure, Step 1 shows you.
- Time and risk: the audit takes an hour. Do the cheap fixes (links, titles, status codes) first, and treat a rendering change as its own release with its own rollback plan.
- Scope: this guide covers Google. Google's own documentation says not all bots can run JavaScript, so check any other engine that matters to you.
Why does a React single-page app cause SEO problems?
Google's JavaScript documentation describes crawling, rendering and indexing. Pages that return a 200 status are queued for rendering, which can take a few seconds or longer, and the rendered HTML is what gets indexed. With an app shell, where the first HTML has no content, Google must execute JavaScript to see what the page shows.
That pipeline works. The trouble is what can go wrong inside it. The table lists the React-specific causes to check first.
| What you built | What a crawler may see | Fix |
|---|---|---|
Empty <div id="root"> shell |
Nothing until rendering succeeds | Prerender or server-render important routes |
onClick handlers that call navigate() on a div or button |
No link to follow | Use <a href> or your router's link component |
Hash routes such as /#/pricing |
One URL for every view | Use path-based routes with the History API |
One title and description in index.html for every route |
Identical metadata across pages | Set title and meta tags per route |
| Catch-all route that returns 200 for any path | Error pages indexed as soft 404s | Return a real 404, or add noindex to the error view |
Canonical or noindex changed only by script |
Directive may be missed | Put it in the HTML the server sends |
| Hero image added by script | Discovered late, which can slow LCP | Put it in the first HTML |
Step-by-step instructions
Step 1: See what the server actually sends
Fetch the page the way a simple client would, with no script execution:
curl -s https://example.com/pricing | head -n 40
A client-rendered Vite build often returns a shell like this (the script file name varies):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example App</title>
<script type="module" crossorigin src="/assets/index-4f1a2b9c.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
If your headings and text are not in the response, your content depends on rendering. Then open the page in Search Console's URL Inspection tool, run the live test and compare the rendered HTML with what a visitor sees. Our URL Inspection live test guide shows where to look.
You have succeeded when you can state, per page type, whether the content is in the first HTML, only in the rendered HTML, or in neither.
Step 2: Give every view a real URL and a real link
Search engines discover URLs from <a> elements with an href attribute, according to Google. React Router's Link component is documented as an enhanced <a href> wrapper, so it produces a real anchor while still navigating without a page reload. A clickable div does not.
// Crawlable: renders an anchor with an href
<Link to="/pricing">Pricing</Link>
// Not crawlable: nothing for a crawler to follow
<div onClick={() => navigate("/pricing")}>Pricing</div>
Use path-based routing, not hash routing. Google's documentation says to avoid fragments for loading different content and to use the History API; use a router that does so. Also make sure your host can answer a direct request for each path, not only the home page.
You have succeeded when every page you want indexed is reachable through ordinary links, and each URL loads when pasted into a new tab.
Step 3: Set a unique title and description per route
An SPA often keeps the single title from index.html for every page. React's documentation says you can render <title> and <meta> from any component and React places them in the document head. That lets each route own its metadata:
export default function PricingPage() {
return (
<>
<title>Pricing and Plans | Example App</title>
<meta name="description" content="Compare plans, limits and billing options for Example App." />
<h1>Pricing</h1>
{/* page content */}
</>
);
}
Two cautions from the same documentation. Render only one <title> at a time, because with more than one the behavior of browsers and search engines is undefined. And <title> accepts a single string, so build it with a template string, not mixed text and variables. If your React version or setup does not support this, set document.title in an effect or use a head-management library; check your version's documentation. Tags set only in the browser appear in the rendered HTML, not in the first response. See SEO Title Tags and Meta Descriptions for what to write.
You have succeeded when the URL Inspection rendered HTML shows a different title and description for at least three different routes.
Step 4: Return honest status codes for missing pages
A client-side router can display a "Not found" view for any unknown path, but if the server returned 200 for the request, search engines may treat it as a soft 404. Google gives two workarounds for SPAs: redirect with JavaScript to a URL that returns a real 404, or add a noindex robots meta tag to the error view with JavaScript. A real server 404 is better, which usually means either server rendering or a host rule that knows your valid routes. Custom 404 Page for SEO covers the error page itself.
Do not redirect every unknown path to the home page; the soft 404 guide lists that among the causes.
You have succeeded when curl -I https://example.com/this-page-does-not-exist returns 404, or the error view carries noindex.
Step 5: Do not lean on JavaScript for indexing directives
If a page should be indexed, do not ship noindex in the original HTML and plan to remove it with React. Google says it may skip rendering when it sees noindex, so the removal may never run. For canonical URLs, Google prefers them in the HTML and, if JavaScript sets one, it should not differ from the HTML version, and there should only be one. Our canonical URL guide explains the rules.
You have succeeded when robots, canonical and status decisions are readable from the first response or are confirmed identical in the rendered HTML.
Step 6: Pick a rendering route for pages that need search
You have three realistic options, from least to most change.
- Stay client-rendered. Reasonable for logged-in dashboards, tools and pages that do not need search traffic. Apply Steps 2 to 5 and verify.
- Prerender at build time. The build produces static HTML for a list of URLs. React Router's framework mode documents this: a
prerenderfunction returns URLs, and the build generates static HTML for them. Good for marketing pages and documentation whose URLs are known in advance. - Render on the server. A framework renders pages per request or at build time. React's documentation recommends starting new projects with a framework and says frameworks such as Next.js and React Router support client, static and server rendering, including server rendering route by route. The Next.js SEO guide covers one of them in detail.
Google describes dynamic rendering (a rendered copy for crawlers, the client version for users) as a workaround, not a recommended solution, and recommends server-side rendering, static rendering or hydration instead. For the trade-offs between these modes, see Server-Side vs Client-Side Rendering for SEO.
If you add server rendering, the client must hydrate the server's HTML. React's hydrateRoot documentation says the tree must produce the same output as on the server and that mismatches are bugs to fix, not warnings to ignore. Typical causes are checks like typeof window !== 'undefined' in render logic or different data on server and client.
You have succeeded when the important routes return their main heading and text in the first response.
Step 7: Check speed and test the result
Web.dev notes that an image added by script cannot be discovered until the script has run, and calls that "never optimal" for the Largest Contentful Paint element. Keep above-the-fold images in the HTML and split your bundle; see Reduce JavaScript Bundle Size.
Then test: URL Inspection for three page types, a made-up URL for the 404 behavior, and curl for the first HTML. Crawvix includes a check for errors logged to the browser console on load, which helps catch an app that breaks on start; it cannot tell you whether Google will index a page.
You have succeeded when the tests agree and you have written down the result for each page type.
Final verification checklist
- The first HTML or the rendered HTML in URL Inspection shows each key page's heading and main text.
- Navigation uses
<a href>or your router's link component, with path-based URLs. - Each route has its own title and meta description, and only one
<title>renders at a time. - A made-up URL returns 404, or its view carries
noindex. - No
noindexor canonical decision depends on a script that may not run. - robots.txt does not block script or style files.
- The chosen react seo approach (client, prerender or server) is written down per page type.
- The hero image and key content are not discovered only after a large bundle runs.
Common mistakes to avoid
- Navigating with clickable
divelements. Use a real link. - Using hash routes. Move to path routes with the History API.
- Keeping one title for the whole app. Give each route its own.
- Returning 200 for unknown paths. Return a 404 or mark the view
noindex. - Removing
noindexwith a script. Keep the original HTML correct. - Choosing dynamic rendering as the permanent plan. Google calls it a workaround.
Frequently asked questions
Can Google index a React single-page app?
Google says it renders JavaScript and indexes the rendered HTML, so yes, it can. Rendering can be delayed, and problems with links, status codes or blocked files can keep content out, so verify with URL Inspection.
Do I need Next.js for React SEO?
No. A framework helps because it can render HTML on the server or at build time, but a client-rendered app with real links, per-route titles and correct status codes can be indexed. React's own documentation recommends starting new projects with a framework.
Does client-side rendering hurt rankings?
Google does not say it does. It says rendering can take longer and that not all bots run JavaScript. Performance effects depend on your bundle size and how content is discovered; do not expect rank changes from the rendering mode alone.
Should I use a prerendering service for crawlers?
Google calls dynamic rendering a workaround, not a recommended solution, and says serving crawlers completely different content can be treated as cloaking. If you use one as a stopgap, keep the content identical.
How do I test React SEO before launch?
Use curl for the first HTML, the Rich Results Test or URL Inspection for the rendered HTML, and a made-up URL for the 404 check. Our pre-launch QA guide has a broader checklist.
Related guides
- JavaScript SEO: Server Rendering, Client Rendering and Crawlers
- Server-Side vs Client-Side Rendering for SEO
- Next.js SEO: Metadata, Sitemaps and Rendering Choices That Work
- Soft 404 Errors: How to Find and Fix Them in Search Console
- Find and Fix JavaScript Console Errors on Page Load
Official references
- https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics
- https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering
- https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript
- https://react.dev/reference/react-dom/components/title
- https://react.dev/reference/react-dom/client/hydrateRoot
- https://reactrouter.com/start/framework/rendering
Next step: Try the free website sample to see how your own site does on a few of these checks. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.
Crawvix Editorial Team
· Published November 4, 2026 · Last updated November 4, 2026 · 10 min read