Client-Side Rendering and SEO: The Complete Guide (2026)

Saar Twito9 min read
Saar Twito
Saar TwitoFounder & SEO Engineer

Hi, I'm Saar - a software engineer, SEO specialist, and lecturer who loves building tools and teaching tech.

View author profile →

What Is Client-Side Rendering?

Client-side rendering (CSR) is a way of building web pages where the server sends a near-empty HTML file and a JavaScript bundle, and the browser then runs the JavaScript to build the actual page in the DOM. Server-side rendering (SSR) and static site generation (SSG) do the opposite: the server returns fully-rendered HTML with the real content already in it. For SEO, CSR is risky because Googlebot has to render JavaScript to see your content, and most AI crawlers don't render JavaScript at all — meaning a pure-CSR site can be invisible to ChatGPT, Perplexity, and Claude.

Key Facts (TL;DR)

  • Googlebot does render JavaScript using a headless Chromium, but rendering is queued separately from initial crawling. Google has stated this delay is now usually under a few minutes, but it can stretch to days for low-priority URLs.
  • Most AI crawlers don't execute JavaScript. GPTBot, ClaudeBot, PerplexityBot, and similar fetchers read raw HTML only. Pure CSR pages return an empty shell to them.
  • Bingbot renders JS but less reliably than Google. Bing has confirmed JS rendering, but coverage is lower and slower.
  • CSR hurts Core Web Vitals. Time to first content depends on JS download + parse + execution, which inflates LCP and INP.
  • Failed JS = blank page for the crawler. A single broken third-party script can leave Googlebot with empty <body>.
  • Dynamic rendering is deprecated. Google deprecated dynamic rendering as a long-term SEO strategy in 2022 and recommends SSR or SSG instead.

Why CSR Hurts SEO and AI Visibility

CSR creates four distinct problems, each of which can be the dealbreaker on its own:

  1. Two-wave indexing for Google. Googlebot first indexes the raw HTML (which on a CSR app is essentially empty), then queues the page for rendering. Google has reduced this delay since 2020, but it still creates timing issues for fresh content and means JS errors silently kill indexing.
  2. AI crawlers don't render JS. If you care about being cited in ChatGPT, Claude, or Perplexity answers, your content must exist in the raw HTML. CSR pages are invisible to these crawlers — see our SEO vs AEO guide.
  3. Core Web Vitals collapse.CSR pushes content rendering to the client's CPU. LCP (Largest Contentful Paint) is bound by JS execution time. On mid-range Android devices the gap between SSR and CSR is often 2–4 seconds.
  4. Fragility. A single failed third-party script, blocked CDN, or JS parse error can leave the crawler with no content. Server-rendered HTML always survives JS failure.

Rendering Strategies Compared

StrategyWhere HTML is builtGoogle SEOAI crawlersPerformance (LCP)
CSR (pure)Browser, after JS executesRisky — depends on render queueInvisibleWorst
SSRServer, on every requestExcellentExcellentGood (TTFB-bound)
SSG (static)Server, at build timeExcellentExcellentBest
ISR (incremental static)Server, periodically rebuiltExcellentExcellentBest
Hybrid (SSR shell + CSR widgets)Server for content, browser for interactive partsExcellentExcellent (content is server-rendered)Good
Dynamic rendering (deprecated)SSR for bots, CSR for usersWorks, but Google recommends againstWorks for declared botsMixed

How to Tell If Your Site Is Pure CSR

  1. View source (not Inspect). Right-click → "View page source" in Chrome. This is the raw HTML the crawler sees. If <body> is empty or contains only a <div id="root">, your site is pure CSR.
  2. Disable JavaScript and reload.Chrome DevTools → Command Palette → "Disable JavaScript" → reload. If the page is blank, AI crawlers see the same blank page.
  3. Use Google's URL Inspection tool.In Search Console, run "Live test" → "View tested page" → HTML. Compare to "Screenshot" — if the screenshot shows content but the HTML is empty, Google is doing the rendering for you.
  4. curl the URL. Run curl https://yoursite.com/pagefrom the terminal. If you don't see the page's text content, neither do AI crawlers.

Fixing CSR: Migration Paths

If you're on React (CRA, Vite SPA)

Migrate to Next.js (App Router or Pages Router) or Remix. Both render to HTML on the server by default and hydrate on the client. Most components don't need changes — only ones that touch window or localStorage need to be marked client-only.

// Next.js App Router — server component (default)
// Renders to HTML on the server, no JS shipped for content.
export default async function ProductPage({ params }) {
  const product = await fetchProduct(params.id);
  return (
    <article>
      <h1>{product.title}</h1>
      <p>{product.description}</p>
    </article>
  );
}

If you're on Vue

Migrate to Nuxt. Nuxt 3 ships SSR by default and supports SSG via nuxt generate. Most Vue components migrate without changes.

If you're on Svelte

Use SvelteKit. SSR is the default; you opt into client-only mode per route.

If you're on Angular

Enable Angular Universal (now built into the Angular CLI as @angular/ssr). It adds an Express server that renders the app to HTML on each request.

If migrating is impossible

Consider prerendering — a build step that crawls your CSR app with a headless browser and saves static HTML snapshots per route. Tools: Prerender.io, react-snap, Vite SSG. This covers most SEO needs without a framework migration, but it only works for fully static content.

Common Mistakes (Bad vs Good)

Mistake: Loading content via fetch on the client

Bad: Page renders with a spinner, then a useEffect calls /api/article/123 and writes the content. Raw HTML is empty.

Good: Fetch the article on the server (Next.js server component, getServerSideProps, Nuxt asyncData) so the HTML response includes the content.

Mistake: Routing without real <a href>

Bad: Navigation uses onClick handlers and router.push() with no href. Crawlers don't follow.

Good: Use the framework's Link component (Next.js Link, React Router Link, Nuxt NuxtLink) — these output real <a href>. See our crawlable links guide.

Mistake: Setting meta tags client-side only

Bad: Title and description are written by JavaScript after page load. Crawlers that don't render JS see the default <title>.

Good: Set meta tags on the server. In Next.js use export const metadata or generateMetadata. In Nuxt use useHead in a server context.

Mistake: Hiding content behind tabs / accordions that require JS

Bad: Product specs only render when the user clicks a tab. Crawler never clicks.

Good: Render all content in the HTML; use CSS (or progressive enhancement) to show / hide it. The crawler sees the full content.

Mistake: Treating Googlebot as the only audience

Bad:"Google renders JS, we're fine." True for Google — but GPTBot, ClaudeBot, and PerplexityBot don't. You disappear from AI search results.

Good: Make sure your raw HTML contains your real content, not just a JS bundle.

How to Test Your Rendering Setup

  1. curl the URL. curl -A "Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)" https://yoursite.com/page — does the response contain your content?
  2. Disable JS in Chrome DevTools. Reload. Is the page usable?
  3. Search Console URL Inspection → Live Test → View Rendered HTML. This is exactly what Google indexes. Verify your <h1>, body copy, and links are present.
  4. Run Lighthouse SEO audit. Flags missing meta tags, missing headings, and broken links discoverable by crawlers.
  5. Check site:yoursite.com in Google. If only the homepage is indexed despite hundreds of routes, you have a rendering problem.

For a deeper checklist, see our site crawlability guide.

FAQ

Doesn't Google render JavaScript now?

Yes — Googlebot uses a current Chromium and renders JS. But rendering is queued separately from initial crawling, JS errors silently break indexing, and other crawlers (especially AI fetchers) don't render at all. SSR / SSG remain the recommended approach.

Is React bad for SEO?

No. React running through Next.js, Remix, or Gatsby produces server-rendered HTML and is excellent for SEO. React running as a pure SPA (Create React App, Vite SPA without prerendering) is the problem.

What about hydration — does that hurt SEO?

No. Hydration is React / Vue attaching event handlers to already-rendered HTML. The HTML is delivered fully formed, so crawlers see real content. Hydration only affects user interactivity, not crawlability.

Do AI crawlers like GPTBot render JavaScript?

No, generally not. OpenAI, Anthropic, and Perplexity have not committed to JS rendering at scale, and most evidence indicates their crawlers fetch raw HTML only. If you want to be cited in AI answers, your content must be in the initial HTML response.

Is dynamic rendering still OK?

Google deprecated it as a long-term recommendation in 2022. It still works mechanically — serving SSR HTML to bots and CSR to users — but it's a workaround, not a strategy. Migrate to SSR or SSG when you can.

What's the simplest SSR option for a small site?

Static site generation. If your content rarely changes, build the site to static HTML and serve from a CDN. Next.js generateStaticParams, Nuxt nuxt generate, Astro, and 11ty all do this with no server runtime.

Will SSR slow down my site?

It can if you do it wrong (slow database queries on every request). Done right, SSR + caching has lower LCP than CSR because content arrives ready to paint instead of waiting for JS to download and execute.

Do I need to worry about CSR for the admin / dashboard area?

No. Authenticated pages aren't indexed and aren't cited by AI crawlers — CSR is fine there. The rule applies to public, indexable content.

Conclusion

CSR was an acceptable compromise when SSR frameworks were immature. In 2026 it isn't — Next.js, Nuxt, SvelteKit, and Astro all make server-rendered HTML the default with no developer cost, and AI crawlers have made the cost of CSR much higher than it used to be. View source, run curl, disable JS, and check what your content actually looks like in raw HTML. If it's empty, your SEO and AI visibility are at risk regardless of how good the content would be once rendered.