SEO with React: Client-Side Rendering vs SSR/SSG
SEO with React starts with your render mode. See how CSR, SSR, and SSG affect crawling, and how to fix meta tags with Next.js.
SEO with React starts with one decision: does the browser build your page, or does the server? A pure client-side React app ships an HTML shell with a single <div id="root"> and a pile of scripts. Search bots have to run those scripts before they see any content. That costs time, and some bots give up. Server-side rendering (SSR) and static site generation (SSG) skip that wait. Both send real HTML on the first response. Google's own JavaScript SEO documentation confirms the gap. Rendering is a separate, deferred step. It doesn't happen the moment a bot fetches your URL. This guide covers what CSR breaks. It covers how SSR and SSG fix it, and how to wire the Next.js Metadata API so the fix reaches your <head>. For CMS-side fields like canonical URLs and schema markup, see headless CMS SEO instead. This piece covers the React app that renders them, and it's part of the broader React CMS question of how a React front end and a content backend fit together.
What client-side rendering does to a crawler
A CSR app defers everything to JavaScript. The server sends a near-empty document. The browser downloads a bundle. React runs. Only then does content appear in the DOM. A human on a fast connection barely notices. A crawler checking "view source" sees nothing worth indexing.
Google can execute JavaScript. But execution is not instant, and it isn't guaranteed on every page. Per Google's own pipeline, a URL gets crawled first. Then it's queued for rendering. Only after a headless Chromium instance runs your scripts does it get indexed. Google says the queue may hold a page "for a few seconds." It can also take much longer, with no fixed upper bound. Other crawlers, from Bing to the bots that power AI answer engines, render JavaScript less reliably than Google does. A CSR-only page is a bet that every crawler you care about will wait for your bundle. Some won't.
SSR vs SSG vs CSR: the real tradeoffs
None of these is strictly correct. Each trades freshness for speed in its own way. Next.js lets you pick per route instead of per app.
| Mode | HTML ready at | Best for | SEO risk |
|---|---|---|---|
| CSR | After JS runs, client-side | Dashboards, logged-in tools | Content invisible until render completes |
| SSR | Server, on every request | Personalized or frequently changing pages | Slower time to first byte under load |
| SSG | Build time, once | Marketing pages, docs, blog posts | Stale until the next rebuild or revalidation |
A search-facing marketing page almost always wants SSG or SSR. A dashboard behind a login wall has nothing to rank. CSR there costs nothing. The mistake is picking one mode for the whole app and forcing every route through it. Next.js's App Router renders each route independently. A blog post can prerender at build time while a settings page fetches live data on every request.
The Next.js Metadata API replaced next/head, and that matters for crawlers
next/head is a Pages Router tool. It injects tags into the document after React renders, on the client. Anything reading raw HTML before hydration might miss what it added.
The App Router's Metadata API works another way. You export a metadata object or a generateMetadata function from a route file. Next.js builds the <head> on the server.
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.metaDescription,
openGraph: { title: post.title, images: [post.featuredImage] },
};
}
Per Next.js's own docs, resolving generateMetadata is part of rendering the page. If the route can be prerendered and the function introduces no dynamic behavior, the resulting tags land in the page's initial HTML. No race between your script loading and a bot reading <title>. A Draftbase-backed page can call generateMetadata and fetch the entry from the delivery API. That maps metaDescription straight onto the tag Google reads on the first pass, with no client JS involved.
Hydration is where a correct SSR page can still lose ground
Server-rendering the HTML solves the first-paint problem. It creates a second problem. The client has to match that HTML against what React would have rendered on its own. That step is hydration. It runs after the page is already visible.
Two things go wrong here often enough to matter. First, a mismatch between server and client output causes trouble. React discards the DOM it just received and rebuilds it from scratch, which is slower than not server-rendering at all. Second, heavy hydration work blocks the main thread. That shows up as high Interaction to Next Paint (INP), one of Google's Core Web Vitals and a documented ranking input. A page can look done and read as content-rich to a bot. It can still be penalized if it takes seconds before a real click registers.
The fix isn't rendering less on the server. It's shipping less JavaScript that has to match up. React Server Components, available in the Next.js App Router, render on the server. They send no client bundle for that component at all, so there's nothing to hydrate. Reserve "use client" for components that need the live parts of the page, not for anything that happens to import a hook. We cover fetching CMS content straight into server components in how we made CMS content work with React Server Components.
When is CSR actually the right call?
Not every route needs to rank. A logged-in analytics dashboard, an internal admin tool, or a checkout flow behind a session has no organic audience. There's nothing worth crawling there. Forcing SSR onto routes like that adds server cost for zero SEO benefit. CSR is also the honest choice for anything per-user and uncacheable. Prerendering there would just serve a stale shell anyway.
The failure mode isn't choosing CSR. It's choosing it by default, for every route, because that's how the app started. A marketing site bolted onto a CSR single-page app inherits the SPA's blank-shell problem. That hits the exact pages built to be found.
The underused angle: check what Google actually indexed, not what your bundle renders
Most React SEO advice stops at "use SSR." Few people check whether the fix landed. Google Search Console's URL Inspection tool shows the rendered HTML Google captured for a specific URL. That's different from what a browser shows you, and different again from your source code. Compare that rendered output against your page's real content. A gap there means Google's renderer hit something your local dev server didn't. Common culprits: a client-only data fetch with no server fallback. Or a component gated behind a state that never resolves in a headless browser. Or a redirect chain a bot doesn't follow the way a user's browser does.
This is also where the two-wave framing repeated across SEO blogs gets it wrong. It isn't two clean passes with a fixed gap between them. It's a queue. Position in that queue depends on your site's crawl budget and Google's available rendering capacity that day. A small site might render within minutes. A large one with thin crawl budget can sit for a while. Checking the indexed version directly removes the guesswork a fixed-delay mental model adds.
What breaks when you migrate an existing CSR app to SSR
Moving a live React app from CSR to SSR is not a flag you flip. It's more work than that. Code that ran fine in the browser often assumes the browser exists.
Anything that reads window, document, or localStorage at module load time throws on the server. None of those exist in a Node process. The fix is usually a check. Read those values inside useEffect, or behind a typeof window !== 'undefined' guard, not at the top of a component.
Environment variables need a second look too. A CSR app can safely bundle a public API key into client JS. An SSR app renders on your server. A secret key used there never reaches the browser at all, which is a real security upgrade. That only holds if you keep the two separate and don't expose a server-only variable through props.
Third-party scripts are the quiet one. A chat widget or an analytics snippet often assumes document.body is ready when it runs. That breaks silently during server render, since there's no DOM yet to attach to. Load those client-side only, behind the same "use client" boundary you're using for interactive components.
Migrate one route at a time. Next.js's App Router lets SSR and CSR routes live in the same app. Convert your highest-traffic landing pages first. Leave a complex internal tool as CSR until it's worth the rework. If your data source is a CMS rather than a database, using Contentful with React walks through the same per-route pattern against a real API.
Where this leaves your React app
CSR isn't broken. SSR isn't automatically the right pick either. The decision is per route. Does this page need to rank? If so, is its HTML ready before a bot has to run your JavaScript to find out? Get that right first. Then let the Metadata API put your titles and descriptions in that same server-rendered response. Don't race hydration to inject them client-side.
Draftbase's delivery API serves published entries as plain JSON over REST or GraphQL. That's exactly what generateMetadata and a server component both want: data available before render, with nothing to wait on client-side. If you're wiring a React front end to a CMS for the first time, see how to use a headless CMS with React for the setup, then check pricing for where Draftbase fits.
Ship content that's built to be found
Draftbase generates schema, structured data, and a fast MDX editor for every post.
Frequently asked questions
Does client-side rendering hurt SEO?
Yes, for pages meant to rank. A CSR page ships a near-empty HTML shell, and crawlers have to wait in a rendering queue before they see real content. Google confirms rendering is a separate, deferred step, not instant.
What is the difference between SSR and SSG for SEO?
Both send full HTML to the crawler on the first request, so both solve the CSR problem. SSR renders fresh on every request, which suits pages that change often. SSG renders once at build time, which suits pages that mostly don't.
Should I use next/head or the Metadata API in Next.js?
Use the Metadata API. It's the App Router's tool, and it builds your `<head>` on the server before any client code runs. next/head is a Pages Router tool that injects tags after React runs in the browser.
Does hydration affect SEO?
Yes, indirectly. Heavy hydration work blocks the main thread. That raises Interaction to Next Paint, a Core Web Vital tied to ranking. A page can look done and still score poorly if it's slow to respond to a click.
Is client-side rendering ever fine for SEO?
Yes, for pages with no organic audience. A logged-in dashboard or an internal tool has nothing to rank. CSR costs nothing there. The risk is defaulting every route to CSR, including the marketing pages meant to be found.