JavaScript rendering: SSR, CSR, hydration patterns

Google can render JavaScript. That is not the same claim as “Google renders your JavaScript quickly, completely, and on your schedule.” The distance between those two statements is where most JavaScript SEO problems live, and the rendering pattern you choose decides how wide that gap is.

Modern frameworks (React, Vue, Angular, Next.js, Nuxt, SvelteKit) build some or all of their content with JavaScript at runtime. The HTML the server sends first may be a near-empty shell, with the real content injected by script after load. A modern browser handles that fine. A crawler’s experience depends on whether it executes the JavaScript, when it gets to it, and what state the page is in when execution finishes. That comes down to four rendering patterns and one process.

The four rendering patterns and where each sits for SEO #

Four patterns dominate current web development, and they differ mainly in who builds the HTML and when.

Pattern What it does SEO position
SSR (server-side rendering) Server generates full HTML on each request; client receives a complete page. Strong. Crawlers see content in the initial response, no render step required.
SSG (static site generation) Server pre-builds every page at build time; client receives static HTML. Strong, and faster than SSR because there is no per-request work.
CSR (client-side rendering) Server sends a minimal shell; the client's JavaScript builds the page. Weakest. Crawlers must execute JS, and rendering can be delayed or incomplete.
Hybrid (hydration, ISR, streaming) Server pre-renders the HTML; client JavaScript "hydrates" interactivity onto it. Strong when implemented correctly, fragile when it is not.

Single-page applications built without server rendering fall into CSR. Most modern frameworks now default to hybrid patterns because framework authors have shipped better defaults over the years. The practical takeaway is at the top of the table: SSR and SSG put your content in the HTML the crawler receives first, so it is available regardless of whether or when JavaScript runs. That single property is what makes them the safest defaults for organic search.

How Googlebot processes JavaScript, and what it costs #

Google’s own documentation describes indexing of JavaScript sites as a three-stage pipeline: crawl, render, index.

  • Crawl. Googlebot fetches the URL and parses the HTML response, discovering links and resources. If the HTML already contains the content, indexing can proceed without a render step.
  • Render. When content depends on JavaScript execution, the URL waits for the Web Rendering Service, which processes it with a current version of headless Chromium. Only after this does the client-built content become visible to Google.
  • Index. The rendered DOM is what gets indexed. Content that appeared only after JavaScript ran is now included.

The cost of JavaScript rendering is not that Google refuses to do it. It is the render step itself: the delay tax, plus the failure modes it introduces. On timing, the figures have moved. Martin Splitt gave a median render delay near 5 seconds at Chrome Dev Summit 2019, framing the 90th percentile as minutes rather than the week-long waits people feared. The most current public dataset is the 2024 Vercel and MERJ study, which analyzed over 100,000 Googlebot fetches on nextjs.org across April 2024. It reported a 25th percentile of 4 seconds, a median near 10 seconds, and a 75th percentile of 26 seconds, and found that 100 percent of the sampled HTML pages were fully rendered, including content loaded asynchronously through API calls.

Two caveats sit alongside those medians. First, the tail is long: the same study measured a 90th percentile around three hours and a 99th percentile around eighteen hours, so a minority of pages wait far longer than the median implies. Second, higher-authority pages tend to render sooner while low-authority, JavaScript-heavy pages wait; you operate on Google’s render schedule, not your own. The render queue is not the killer it was once claimed to be, but the variance is real, and the failure modes below do not depend on timing at all.

What pure CSR breaks #

Client-side rendering with no server fallback produces a recurring set of problems.

  • Slower, less certain indexation. CSR pages depend on the render step; the median wait is short, but the tail can stretch, and low-authority sites feel it most.
  • Incomplete indexation on failure. If rendering times out or errors (heavy scripts, blocked resources, runtime exceptions), Google indexes whatever was in the HTML response, which may be close to empty.
  • Metadata read from the raw HTML. Some signals, including the response used for canonical and title evaluation, are read before rendering. Titles, descriptions, and structured data injected by JavaScript can be missed or misread.
  • Delayed link discovery. Links injected by JavaScript surface only after render. When navigation is entirely script-built, discovery slows across the whole site, which is precisely the crawl-efficiency problem that crawl budget work on large sites tries to protect.
  • Broken social previews. Facebook, X, LinkedIn, and Slack preview crawlers do not execute JavaScript at all, so CSR pages produce empty previews when shared.

The combined effect: pure CSR is acceptable where organic search is a minor channel and most traffic arrives directly. Where search matters, CSR is the wrong default.

Why hybrid patterns dominate, and what hydration risks #

Frameworks that handle SEO well converge on one answer: render the initial HTML on the server or at build time, then let JavaScript take over for interactivity. Next.js with server or static rendering, Nuxt with SSR, SvelteKit with a server or static adapter, Remix with loader functions, and Astro with its islands approach all express the same idea. The crawler sees content without executing JavaScript; the user still gets the interactivity JavaScript provides after load. The trade-off is real: server resources per request for SSR, longer builds for SSG, and more complexity than a plain SPA. That cost buys the indexability.

Hydration is where hybrid patterns go wrong. It is the process of client-side JavaScript attaching to server-rendered HTML, adding event listeners and reactive behavior without rebuilding the DOM. When the server-rendered and client-rendered output disagree, the framework can discard the server HTML and re-render on the client, a hydration mismatch. The symptoms are subtle: content flashing as one version replaces another, layout shifts that hurt CLS, interactive elements that render but never respond, and, worst for SEO, a divergence where the HTML response holds one version of the content and the post-hydration DOM holds another.

Google’s guidance is to keep server and client output consistent for the parts that affect search: content, metadata, and links. A common cause of mismatches is non-deterministic code running on both sides, so avoid values like the current time or random numbers in components that render on both. Differences in non-SEO elements, such as analytics tags or an A/B variant applied after hydration, are usually acceptable.

How to check what Google actually renders #

The check compares the raw HTML response against the rendered DOM and confirms your SEO-critical content lives in the former.

  1. View source, not the DOM. Whatever is missing from the raw HTML (the response before JavaScript) is JavaScript-dependent, and that is your risk surface.
  2. Disable JavaScript in DevTools and reload. What remains visible is roughly what a non-executing crawler and every social preview bot will see. If your title, body copy, or primary navigation vanishes, so does it for them.
  3. Use Google’s own rendering tools. The URL Inspection tool in Search Console and the Rich Results Test show the rendered HTML Google produced; comparing that against your source reveals exactly what depended on JavaScript.
  4. Confirm rendering resources are crawlable. The JavaScript, CSS, and API endpoints needed to build the page must not be blocked by robots.txt. Blocking them blocks the render.

Run this before and after any framework change. It is the difference between assuming Google sees your content and knowing it does.

Fixing an existing CSR site #

Migration paths vary, but the options fall in a clear order of preference now that Google has changed its stance on one of them.

  • Move to a hybrid framework. React applications migrate to Next.js, Vue applications to Nuxt. Non-trivial, but it fixes the problem at the root and rarely needs revisiting.
  • Pre-render or convert the high-value pages first. The homepage, category, and product pages move to SSR or static generation while the long tail stays on CSR until impact justifies more work.
  • Keep the app on CSR and surface SEO elsewhere. When search is not a priority for the application itself, put marketing pages, the blog, and the help center on server rendering and leave the app as-is.

Dynamic rendering, serving pre-rendered HTML to crawlers while users get the CSR app, used to be the standard bridge here. It is worth being explicit: Google now labels dynamic rendering a workaround rather than a recommendation and steers new work toward server-side rendering, static rendering, or hydration instead. It still functions as a temporary fallback when full migration is not feasible, but it adds a crawler-versus-user divergence and extra infrastructure, and it should not be the first choice for anything new.

FAQ #

Can Google index a client-side rendered page at all?
Yes. Google renders JavaScript with headless Chromium and, in the 2024 Vercel and MERJ sample, rendered 100 percent of tested HTML pages including async content. The issue is not capability; it is the added render step, its timing tail, and the failure modes when rendering is heavy, blocked, or inconsistent.

Is the “two waves of indexing” idea still accurate?
Google historically described indexing JavaScript pages in two passes, an initial HTML index and a later render-based one. How Googlebot’s two-wave indexing works is its own topic; here the practical point is only that CSR content depends on the second, render-dependent pass, which is exactly the delay SSR and SSG avoid.

Does streaming SSR hurt indexing?
It should not, as long as SEO-critical content (headings, body copy, meta tags) is in the initial HTML shell rather than in a chunk that streams in after a Suspense boundary. Keep non-critical content, like recommendations or comments, in the streamed portion.

The rendering decision is architectural, not a fix-it-later concern. Picking a framework with strong SSR or SSG defaults produces indexable, previewable output before you write a line of SEO-specific code, while a pure CSR choice means paying a slow-indexation and broken-preview tax every time you publish. Start there, verify with the rendered-HTML comparison above, and most JavaScript SEO problems never appear.