WebPublished 12/13/2025

CSR vs. SSR vs. SSG: How Rendering Strategies Really Differ

Starting from how a browser actually gets a page, this breaks down when each of CSR, SSR, and SSG produces HTML, what it costs you, and how to pick the right one for your project.

Sooner or later you'll run into this question: should this page be CSR, SSR, or just SSG'd into static files? The three acronyms sound similar, and most comparison posts just throw jargon at you and call it a day. But if you don't actually understand what sets them apart, picking one comes down to a guess.

This post starts from the most basic question there is: when — and by whom — does a page's HTML actually get filled with content? Once that clicks, the differences and trade-offs between CSR, SSR, and SSG become pretty intuitive.

First, what does the browser actually need to show you a page?

When you load a page, the browser roughly does this: fetch the HTML, parse it into a DOM tree, fetch the CSS and JavaScript, run the JavaScript, then paint the screen. Somewhere in that pipeline, the HTML becomes "ready" — and exactly when that happens is the core thing that separates the three rendering strategies.

Let's clear up a common misconception first: with all three — CSR, SSR, and SSG — the browser gets an HTML response the moment the page opens. The difference isn't "whether you get HTML" or "how fast it arrives." It actually comes down to two questions:

  • Does the HTML that reaches the browser already have content in it?
  • When — and by whom — was that content-filled version computed?

Follow those two questions and the three strategies fall right out:

  • CSR (Client-Side Rendering): the server sends back a near-empty shell — often just a <div id="root"></div> plus a pile of <script> tags. Content only shows up after the browser runs the JavaScript and uses it to build out the DOM.
  • SSR (Server-Side Rendering): the server computes a fully content-filled HTML page on the fly, at the moment the request comes in, and sends that back.
  • SSG (Static Site Generation): also fully content-filled HTML, but it's computed ahead of time, during the project's build step, and saved as a file. Every user just gets the same pre-built file.

Same content, but when it gets filled in — and who does the filling — makes for wildly different experiences and costs. Let's go through each one.

CSR: pushing the rendering work onto the browser

CSR's approach is straightforward: the server sends down a nearly empty HTML shell plus a JavaScript bundle. Only after the browser downloads and runs that JavaScript does it dynamically stuff content into the DOM.

A typical first response from a CSR server looks something like this:

html
<!-- The HTML the server sends back is nearly empty -->
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>

The actual content only shows up once bundle.js runs and a framework like React or Vue mounts its components onto #root. That's why on CSR sites you'll typically see a blank screen or a loading spinner first, and the full page "pops in" a beat later.

The upside of CSR: once that JavaScript is downloaded, subsequent page transitions and interactions barely need to ask the server for a whole new HTML page. That makes the experience feel smooth, almost like using a native app — which is exactly how SPAs (Single-Page Applications) work under the hood.

The cost is just as obvious:

  • Slow first load: users have to wait for JavaScript to download, parse, and run before they see any content — that wait drags down FCP (First Contentful Paint). Component mounting and event binding only happen once the JS finishes, so TTI (Time to Interactive) — the point where the page is actually clickable — lands even later than FCP. Both of these waits get a lot worse on slow networks or weak devices.
  • Bad for SEO: the first HTML a search crawler sees is nearly empty. Google will run your JavaScript and take a second pass, but that pass is slower, less reliable, and not every crawler even bothers.

SSR: the server computes the HTML on the spot

SSR flips this around and moves the rendering work back to the server. Every time a request comes in, the server runs the rendering logic on the spot, assembles a complete HTML page, and sends it back.

js
// Server side — re-renders on every incoming request
app.get('/product/:id', async (req, res) => {
  const product = await fetchProduct(req.params.id) // fetch the latest data right now
  const html = renderToString(<ProductPage product={product} />)
  res.send(html)
})

When a user opens the page, they get a fully content-filled HTML document right away, so the screen can paint immediately — that's FCP (First Contentful Paint), and it's usually a lot faster under SSR than CSR. Search crawlers can also read the full content directly without having to execute any JavaScript, which makes SSR SEO-friendly by default.

But SSR isn't free either:

  • Every request means recomputing everything. Server load scales linearly with traffic, so you need extra server capacity and a caching strategy to keep up.
  • Response time is at the mercy of your database and APIs. If rendering needs to fetch data and that data source is slow, the whole page's TTFB (Time to First Byte) slows down with it.
  • The HTML that comes back is complete, but it still isn't clickable until the JavaScript loads and hydration wires up the event listeners — a process called Hydration, where static HTML gets reconnected to the front-end framework's logic. That wait is shorter than CSR's, but it's not zero.

SSG: generating the HTML ahead of time, at build

SSG moves that on-the-fly computation earlier, into the build step. When you run the build command, the tooling pre-generates the HTML for every page and saves it as a batch of static files. After that, no matter how many users show up, the server just hands out the same file, untouched, every time.

js
// Runs once at build time; every user afterward gets the same result
export async function getStaticProps() {
  const posts = await fetchAllPosts()
  return { props: { posts } }
}

Because what's being served is just a static file baked ahead of time, SSG usually delivers the best performance of the three: there's no server-side computation to wait on, and you can drop the files straight onto a CDN so users anywhere in the world get served from the nearest edge node. It's just as SEO-friendly as SSR, too — crawlers get the same complete content.

SSG's biggest limitation is data freshness: the content is a snapshot from whatever the database looked like at build time. Update the database afterward, and the page won't reflect it — not until you run another build. That makes SSG a great fit for blog posts, docs, and marketing pages, where content doesn't change often, but a poor fit for anything that changes minute to minute, like stock prices or cart inventory.

That limitation is also why ISR (Incremental Static Regeneration) exists as a middle ground — it regenerates static pages in the background on a fixed interval, balancing speed against freshness. That's a whole other topic beyond SSG itself, though, so we'll just leave it here for now.

Putting all three side by side

Lining up first-load speed, SEO, server load, and data freshness makes the trade-offs a lot clearer:

AspectCSRSSRSSG
When HTML is generatedIn the user's browser, at runtimePer request, on the serverAt build time, ahead of use
First-load speedSlower — waits on JS executionFast, but depends on server and data-source speedFastest — serves a static file directly
SEO friendlinessPoorGoodGood
Server loadLow (barely any computation)High (computes on every request)Very low (computed once)
Data freshnessReal-timeReal-timeA snapshot from build time
Good fit forDashboards, highly interactive appsFrequently changing or personalized pagesBlogs, docs, marketing pages — stable content

None of these three is objectively more advanced than the others — each one optimizes for a different set of trade-offs. In practice, plenty of frameworks (Next.js, Nuxt.js, and others) let you mix CSR, SSR, and SSG within the same project, choosing per page based on what that page actually needs. You don't have to commit the whole project to one strategy.

So how do you choose?

When you're picking a strategy, start by asking yourself a few questions:

  • Does this page need to be indexed by search engines? If it does — think product pages, blog posts — rule out pure CSR right away.
  • How often does the content change? If it's static or rarely changes (docs, blog posts), SSG is usually your most cost-effective option. If every user sees something different, or the data is constantly changing (stock prices, personalized recommendations), lean toward SSR.
  • Does this page even need SEO, or is it just for logged-in users? For admin dashboards and back-office tools — highly interactive, never indexed — CSR is actually the best and cheapest fit.

In other words, the choice isn't about which technology is cooler — it comes down to the page itself: does it need to be seen by search engines, how often does its content change, and do your users care more about first-paint speed or the smoothness of ongoing interaction?

Wrapping up

The core difference between CSR, SSR, and SSG really comes down to one thing: when — and by whom — the content-filled version of the HTML gets computed. CSR leaves that work to the user's browser, buying a smooth, app-like interaction experience at the cost of first-load speed and SEO. SSR moves that work to the server and computes it live, balancing fresh content with good SEO, but adding server load. SSG does that work upfront at build time, buying the fastest possible speed at the cost of the content being just a static snapshot. None of them is the universally correct answer — understanding what each one trades away is what lets you pick the right one for what your page actually needs.