---
title: "React SEO: Making Client-Rendered Apps Crawlable"
description: "React SEO guide: why client-side rendering hurts crawlability and how SSR, SSG, frameworks, and metadata fix it so search and AI crawlers can index you."
author: "Alec Lindsay"
date: "2026-06-23"
tags: "React SEO, SEO for Developers, Technical SEO"
url: "https://seoagent.com/blog/react-seo"
---
# React SEO: Making Client-Rendered Apps Crawlable
**TL;DR —** A default client-rendered React app serves nearly empty HTML and builds the page in the browser, which means search engines and AI crawlers can see a blank shell. The fix is to render meaningful HTML on the server — via SSR, SSG, or a framework that does it for you — plus real metadata and structured data.
- **The problem:** client-side rendering (CSR) hands crawlers an empty `
` and a JavaScript bundle.
- **The fix:** server-render or pre-render your content with Next.js, Remix, or Astro, and set per-page metadata.
- **The catch:** Googlebot renders JavaScript (slowly); most AI crawlers don't run it at all.
If you build with React and care about organic and AI search, this is one of the highest-leverage technical issues to get right. It's a core piece of [SEO for developers](/blog/seo-for-developers) — the kind of problem that's invisible in the browser but decides whether you ever get indexed.
## The core React SEO problem
Open the page source of a plain Create React App or Vite single-page app and you'll find something like this:
```html
```
That's it. No headline, no copy, no links — just an empty container and a script tag. Your content only exists *after* the browser downloads the bundle, executes it, fetches data, and React paints the DOM. This is **client-side rendering**: the server sends a near-empty document and the client does all the work.
A human with a fast device barely notices. A crawler is a different story.
**Search engines.** Googlebot can render JavaScript, but it does so in a deferred, two-wave process: it crawls the raw HTML first, then queues the page for rendering when resources free up. That render can lag hours or days behind, and anything that breaks — a slow API, a JS error, a blocked script — can leave your content unseen. Bing and most other engines render JavaScript far less reliably, if at all.
**AI crawlers.** This is the sharper edge in 2026. The bots behind AI answers — GPTBot, ClaudeBot, PerplexityBot, Google-Extended — overwhelmingly fetch raw HTML and **don't execute JavaScript**. If your content isn't in the initial HTML response, it effectively doesn't exist to them. As more discovery shifts to AI assistants, a CSR-only app risks being invisible exactly where attention is moving.
So the failure mode is simple: **your content is real, but the machines that decide visibility never see it.**
## Why it matters
Three concrete consequences follow from shipping empty initial HTML:
- **Delayed or skipped indexing.** Pages that depend on a second render wave get indexed later, or get dropped when rendering fails.
- **Weak or missing snippets.** Without server-rendered `
` and meta description, your search result can show a generic or empty snippet.
- **Zero AI citations.** If ClaudeBot or GPTBot can't read your page, you can't be summarized or cited in an AI answer.
For a marketing site, docs, or blog, this is the whole game. Crawlability isn't a nice-to-have — it's the precondition for everything else you do in SEO.
## The fixes, step by step
The goal is the same in every case: make sure the crawler receives **meaningful HTML in the first response.** Here are the approaches, roughly in order of how most teams should reach for them.
### 1. Choose a rendering strategy
These are the three core strategies and how they affect crawlability:
| Strategy | When HTML is built | Crawler sees content | Best for |
| --- | --- | --- | --- |
| **CSR** (client-side) | In the browser, after JS runs | Only if the bot executes JS | App-like dashboards behind auth |
| **SSR** (server-side) | On each request, on the server | Yes, immediately | Dynamic, frequently changing pages |
| **SSG** (static generation) | Once, at build time | Yes, immediately | Blogs, docs, marketing pages |
CSR is fine for the logged-in parts of a product no one needs to find in search. For anything you want crawled, you want SSR or SSG so the HTML arrives populated.
### 2. Reach for a framework
You can wire up SSR by hand with `renderToString`, but in practice almost no one should. A React meta-framework gives you server rendering, routing, and metadata handling out of the box:
- **Next.js** — the default choice for most React teams. The App Router server-renders by default; you opt into client behavior with `'use client'`. Supports SSR, SSG, and incremental static regeneration in one app.
- **Remix / React Router** — server-rendering-first, with a strong data-loading model that keeps content in the initial HTML.
- **Astro** — ships zero JavaScript by default and renders components to static HTML, with React "islands" only where you need interactivity. Excellent for content-heavy sites.
If you're starting a new content or marketing site in React, pick one of these rather than a bare SPA. It removes the entire class of problem this article is about. If you're on [Vercel](/vercel), Next.js is the path of least resistance.
### 3. Pre-render an existing SPA (the stopgap)
Already shipped a client-rendered app and can't migrate yet? You have intermediate options:
- **Static prerendering at build time** — tools that crawl your routes during the build and save a fully rendered HTML snapshot per page. Good for sites with a finite, mostly static set of URLs.
- **Dynamic rendering** — detect crawler user-agents and serve them a server-rendered or pre-rendered version while users get the SPA. Google treats this as a legitimate workaround, but it's a maintenance burden and a stopgap, not a destination. Prefer real SSR/SSG when you can.
### 4. Set real metadata per page
Server-rendered HTML is necessary but not sufficient — each page still needs its own ``, meta description, canonical, and Open Graph tags in that initial response.
- **In a framework:** use the built-in head/metadata API (Next.js `metadata` export or `generateMetadata`, Remix `meta`, Astro frontmatter). These render the tags on the server, where crawlers will see them.
- **In a plain SPA:** a library like `react-helmet-async` manages document head tags — but remember it only helps crawlers if those tags exist in server-rendered or pre-rendered HTML, not just after hydration.
Per-page metadata is what turns a crawlable page into a good search result.
### 5. Add structured data
Structured data (JSON-LD) helps both search engines and AI crawlers understand what a page *is* — an article, a product, an FAQ. Inject a `