React SEO
Last reviewed
A React app built as a client-side single page app sends crawlers an almost empty HTML file, and AI crawlers like GPTBot and ClaudeBot do not run the JavaScript that fills it. To fix it, send finished HTML: render on the server (Next.js, React Router), prerender at build time, or put a prerender service in front of the app.
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.
At a glance
| Approach | Fixes content for AI crawlers | Fixes titles and meta tags | Effort | Best for |
|---|---|---|---|---|
| Server-side rendering or static generation (Next.js, React Router) | Yes | Yes | Rewrite or migration | New apps, apps you are rebuilding anyway |
| Build-time prerender (react-snap and similar) | Yes, for pages known at build time | Yes, if set in the rendered page | Low to medium | Small sites with few, mostly static pages |
React Helmet or React 19 <title> alone |
No | Only for crawlers that run JavaScript | Low | Managing the head together with one of the other options |
| Prerender service (Prebaked) | Yes | Yes | Low, one snippet | Existing SPAs where a rewrite is not worth it |
Is React SEO friendly?
React itself is a UI library. Whether a React site is SEO friendly depends on how its HTML is produced.
- Client-side rendering (Create React App style, plain Vite + React) sends a short HTML file with a root
divand a script. Content appears after the JavaScript runs. - Google renders JavaScript. It queues pages and runs them in a headless Chromium "once Google's resources allow". Vercel and MERJ measured in April 2024 that Google fully rendered 100% of the HTML pages they studied on nextjs.org, with a median delay of 10 seconds and a 99th percentile of about 18 hours.
- AI crawlers do not. Vercel and MERJ found in December 2024 that "none of the major AI crawlers currently render JavaScript". That covers OpenAI, Anthropic, Meta, ByteDance and Perplexity crawlers.
So a client-rendered React app can be indexed by Google and still look empty to ChatGPT, Claude and Perplexity. Google itself says "server-side or pre-rendering is still a great idea" because not all bots can run JavaScript. More on how AI crawlers see your site.
How to check your React app
- Run the free checker. It compares your page without JavaScript and in a real browser: word count, headings and a verdict.
See what ChatGPT sees on your site
- Fetch the raw HTML and look for your content.
curl -s https://www.<your-domain>/ | grep -c "<h1"
0 means the heading is added by JavaScript, and crawlers that do not run it will not see it.
- For Google, use the URL Inspection tool in Search Console to see the rendered HTML.
Also check the basics that matter for every React app: links are <a href="..."> elements (Google "can only crawl your link if it's an <a> HTML element" with an href), routes use the History API rather than # fragments, and unknown routes return a real 404 or noindex instead of a 200 error page.
Your options, honestly
1. Server-side rendering or static generation
The React team recommends "starting with a framework" for new apps and lists Next.js (App Router) and React Router (v7), with TanStack Start and RedwoodSDK as up-and-coming options. These frameworks support client-side rendering, static generation and server-side rendering per route. In the Next.js App Router, pages are Server Components by default and are rendered to HTML on the server.
Choose this when you are starting a new app or are already planning a rebuild. It gives the best result for every crawler and usually a faster first load for people.
The cost: moving an existing SPA means changing routing, data fetching and hosting. Server-rendered routes need a server or a platform that runs one.
2. Build-time prerender (react-snap)
react-snap "pre-renders a web app into static HTML" after your build, using headless Chrome to crawl links from the root. You get one HTML file per route.
Choose this when the site has a small, fixed set of pages and content changes only when you deploy.
Limits listed in its README: it works only with HTML5 History API routing, async components can flash a loading state, and headless Chrome can fail in containers. Pages that do not exist at build time, such as new products from an API, are not prerendered until the next build.
3. React Helmet and the React 19 head tags
React Helmet is "a document head manager for React": it sets <title>, meta tags and canonical links from your components. React now does the same natively: "You can render <title> from any component and React will always place the corresponding DOM element in the document head."
What it does not do: in a client-rendered app these tags are still written by JavaScript. Google can pick them up after rendering. Crawlers that do not run JavaScript, including the major AI crawlers, see the tags in your static index.html only. Helmet's own README tells you to call Helmet.renderStatic() on the server when you server render. Use Helmet or the native tags together with options 1, 2 or 4, not instead of them.
4. A prerender service
A prerender service renders your pages in a real browser and gives the HTML to crawlers. People still get your normal React app. Google calls this dynamic rendering and describes it as "a workaround and not a long-term solution", recommending server-side rendering, static rendering or hydration for the long term. It also says that as long as the content is similar, "Googlebot won't view dynamic rendering as cloaking".
Choose this when you have a working React SPA, content that changes between deploys, and no plan to migrate to a framework soon.
When Prebaked is the right choice
Prebaked is a prerender service built for this case: an existing React SPA that should be readable to search engines and AI crawlers without a rewrite.
- One snippet: Express with prerender-node, Cloudflare Worker, Vercel middleware or another integration.
- Same page for bots and people: Prebaked renders your page in Chromium and does not change its content.
- Never worse than today: if Prebaked is slow or down, crawlers get your original page.
- Status codes kept: a page that renders as a 404 returns 404 to the crawler. You can set it with the
prerender-status-codemeta tag. - Cache hits are free. You pay for fresh renders only. You can warm the cache from your sitemap.
If you are starting from scratch, a framework with server rendering is usually the better choice. Prebaked is for the apps that already exist.
FAQ
Is React bad for SEO?
No. Client-side rendering is the issue, not React. Google renders JavaScript, but AI crawlers do not, so a client-rendered React app can look empty to them. Server rendering, build-time prerendering or a prerender service fixes that.
Does React Helmet fix React SEO?
Only part of it. React Helmet manages titles and meta tags, but in a client-rendered app they are still set by JavaScript. Crawlers that do not run JavaScript do not see them, and they do not see your page content either.
Should I use react-snap or a prerender service?
react-snap suits small sites whose pages are known at build time and change only on deploy. A prerender service suits sites with many pages or content that changes between deploys, because pages are rendered when crawlers ask for them and refreshed from the cache.
Do I need Next.js for React SEO?
Not necessarily. Next.js and React Router give the best long-term result for new apps. For an existing SPA, a prerender service gives crawlers the same finished HTML without a migration.
Can ChatGPT see my React app?
If the app is client-rendered, ChatGPT's crawlers get the HTML before JavaScript runs, which is often just an empty root element. Check your page with the free checker.