Skip to content

SSR vs CSR: The Question Is No Longer Binary

Server-side and client-side rendering are the two ends of a spectrum, and most of the interesting options sit between them. The choice depends on the route, not the application.

Share
Five clusters of geometric blocks along a single glowing track, fully assembled at one end and progressively scattered towards the other

Server-side rendering and client-side rendering get framed as a choice between two options, with a list of pros and cons for each. That framing was reasonable when they were the only two things you could do. It isn't now, and it hides the decisions that actually matter.

What varies isn't a binary. It's two independent questions: where the HTML gets generated, and when. Answer those separately, and you get a spectrum, with SSR and CSR at the ends and most of the useful territory in between.

It also isn't one decision for a whole application. Your marketing pages, your product listings, and your account dashboard have completely different requirements, and modern frameworks let you choose per route. Applying one strategy everywhere is how you end up with a slow dashboard or an unindexable landing page.

The spectrum

Static generation: HTML is built once, at deploy time, and served from a CDN. Nothing renders per request. This is the fastest option and the cheapest to host, because a CDN serving a file is nearly free. Limited to content that's the same for everyone and doesn't change between deploys.

Incremental or on-demand regeneration: Static, but pages can be rebuilt on a schedule or when the underlying data changes. You keep CDN speed and avoid redeploying for every content change. Most content sites should use this, and many don't.

Server rendering: HTML is generated per request. Necessary when the page depends on who's asking: personalised content, authenticated views, anything genuinely live. It costs server time per request and takes longer to first byte than serving a file.

Streaming server rendering: The server sends HTML in chunks as it becomes ready rather than waiting for the whole page. The shell arrives immediately; slower sections fill in behind it. Turns one slow database query from a delay on everything into a delay on one region.

Islands: The page is server-rendered HTML with interactivity added only to specific components. The rest ships no JavaScript. Astro popularised this, and for content-heavy pages with a few interactive pieces it's difficult to beat.

React Server Components and partial prerendering: Components render on the server and send a description of the result rather than the JavaScript to produce it, with client components marked explicitly. Partial prerendering goes further, serving a static shell instantly and streaming the dynamic parts. This is where Next.js has been heading.

Client rendering: The server sends a near-empty HTML file and a JavaScript bundle that builds everything in the browser. Slowest first paint, no HTML for anything that can't run JavaScript, and genuinely the right answer for some applications.

Hydration, which is the cost nobody mentions

Here's the part missing from most comparisons, and it explains more of the trade-off than anything else.

Server-rendered HTML isn't interactive on arrival. It's a picture of the page. To make buttons work, the browser downloads the same component code the server just ran, executes it, and attaches event handlers to the existing DOM. That's hydration.

Which means server rendering doesn't remove the JavaScript cost. It moves the first paint earlier and leaves the interactivity cost exactly where it was. There's a window where the page looks finished and doesn't respond, and it's worse on slow devices, precisely where the fast first paint helped most. Users tap, nothing happens, and they tap again.

This is why "SSR is faster" needs qualifying. Faster to appear, yes. Faster to become usable, often not.

The approaches above address this differently. Islands hydrate only the interactive components, so most of the page never needs it. Server Components send rendered output instead of component code for anything non-interactive. Some frameworks pursue resumability, serialising state so the browser can continue rather than re-executing.

Understanding hydration also corrects the most common error in this comparison: that server rendering limits interactivity. It doesn't. A server-rendered React application hydrates into exactly the same application you'd get from client rendering. The difference is what the user sees before that happens.

The metrics that actually decide it

Vague talk about "load speed" hides the trade-off. Four measurements make it concrete.

Time to First Byte is how long it takes the server to respond. Static generation wins outright because a CDN serves a file. Server rendering is worst, since work happens before anything is sent. Client rendering sits in between, sending a small file quickly.

First Contentful Paint and Largest Contentful Paint measure when content appears. Static and server rendering win comfortably; client rendering loses badly, because nothing appears until the bundle downloads and executes. On most pages, the LCP element is an image, which is why image optimisation carries so much weight regardless of strategy.

Interaction to Next Paint measures responsiveness. This is where hydration cost surfaces and where heavy client-side applications struggle, especially on mid-range phones, where the constraint is CPU rather than bandwidth. I went into that in the piece on designing for mobile.

Cumulative Layout Shift measures stability. Client rendering tends to be worse, because content appearing after the fact pushes things around.

Notice no single strategy wins all four. Static generation comes closest and applies to the least content.

What crawlers actually do

The original claim that "search engines prefer HTML" is true but too vague to act on. The precise version matters.

Google executes JavaScript reasonably well, but only in a second pass after the initial crawl, using a finite budget. That introduces delay and variability, worst for pages deep in a large site.

Everything else is the bigger problem. Many crawlers don't execute JavaScript at all: social preview fetchers, several AI crawlers, various search engines, and most third-party tools. To all of them, a client-rendered page is a loading spinner.

The quickest check is to disable JavaScript and load the page, or fetch it with curl and read the HTML. If your content isn't there, that's your answer. I covered the rest of the crawler picture in the piece on SEO for front-end developers.

Server cost, honestly

"CSR reduces server load" is half true and worth correcting.

You've moved rendering off the server. You haven't moved data fetching, so the API still handles every request, and often more of them, because a client-rendered page frequently makes several calls where a server-rendered one made a single internal query.

The real cost story is caching. A statically generated page cached at a CDN costs almost nothing per request and scales indefinitely. A server-rendered page costs CPU on every hit unless you cache it, at which point you're mostly back to static.

So infrastructure cost order is roughly: static cheapest, client rendering next, server rendering most expensive. And per-route caching changes that picture more than the rendering choice does.

Choosing, per route

The practical framework. For each route, ask three questions.

Is the content the same for everyone? If yes, generate it statically. This covers marketing pages, blog posts, documentation, and most product pages. It's the cheapest and fastest option, and it's chronically underused.

Does it need to be indexed or shared? If yes, HTML must exist without JavaScript. That rules out client rendering and leaves static, server, or islands.

How much of the page is genuinely interactive? If it's a few components in a mostly static page, islands. If it's interactive throughout, server rendering with hydration, or client rendering behind authentication.

Worked through, a typical application lands like this: marketing pages static, blog and documentation static, product listings statically generated with on-demand revalidation, product pages server-rendered if personalised, checkout server-rendered, and the account dashboard client-rendered because it's behind a login and nobody needs to index it.

That's five strategies in one application, which is normal and which the binary framing can't express.

When client rendering is genuinely right

It has a real place, and the current enthusiasm for server rendering sometimes obscures it.

Anything behind authentication: Dashboards, admin panels, internal tools. Nothing to index; users are logged in and willing to wait a moment, and interactivity dominates.

Editors and design tools: Highly stateful applications where the initial load is a small fraction of a long session.

Offline-capable applications: If it must work without a network, the logic has to be in the browser.

Real-time interfaces: Chat, collaborative editing, live data. The server is streaming updates, not rendering pages.

The pattern: client rendering suits applications people use, and suits documents people visit badly. Most of the web is documents.

Correcting the myths

Collected, since they circulate widely.

"SSR limits interactivity." No. Hydration produces the same interactive application. What differs is what appears before the JavaScript arrives.

"CSR reduces server load." Partly. Rendering moves; data fetching doesn't, and the API often does more work.

"SSR is always faster." Faster to first paint. Slower time to first byte, and no faster to interactive.

"You have to pick one." You don't, and picking one for a whole application is the actual mistake.

The short version

Stop treating this as two options. Where HTML is generated and when are separate questions, and the answers differ per route.

Default to static generation, because it's fastest and cheapest, and reach for server rendering only when the page depends on who's asking. Use islands when a mostly-static page needs a few interactive pieces. Reserve client rendering for authenticated, highly interactive applications where nothing needs indexing.

And remember hydration, since server rendering moves the first paint earlier without moving the interactivity cost at all. That gap is where most of the real trade-off lives, and it's the thing the pros-and-cons framing has never captured.