Back to Blog
Frontend12 min readJun 2026

Rendering Strategies: CSR, SSR, SSG & ISR

Where your HTML actually gets built, in the browser, on the server per request, or at build time, and how that one choice shapes your first paint, your SEO, your server bill, and your complexity.

FrontendRenderingSSRPerformance
SB

Sri Balaji

Founder · TheSimplifiedTech

On this page

The beautiful app nobody could find

You build a gorgeous single-page app. Smooth transitions, instant client-side routing, a design the team is proud of. You ship it. Then two things happen. First, you search for it on Google and the result is a blank page with a title, because the crawler saw an empty <div id="root"> and a pile of JavaScript it never ran. Second, a colleague opens it on a mid-range phone over 4G and stares at a white screen for four seconds while a 600 KB bundle downloads, parses, and finally paints the UI.

Nothing is broken. The code is fine. The problem is where the HTML got built. Your app decided to build it in the browser, at the worst possible moment, on the slowest device. That decision, the rendering strategy, is one of the highest-leverage choices in frontend, and most people make it by accident.

Who this is for

Frontend developers who've shipped a React/Vue/Angular app and heard "SSR", "SSG", "hydration", and "ISR" thrown around without a clear map. By the end you'll know exactly where your HTML is produced for each strategy, what each one costs, and how to pick. No framework lock-in, the ideas apply everywhere.

One sentence, then a kitchen

A rendering strategy is just the answer to one question: where and when is the HTML for this page built, in the browser, on the server per request, or once at build time?
The whole article in a sentence

Think of your HTML like food at a restaurant. The same dish can reach the customer three different ways, and each has a real trade-off between freshness, speed, and cost.

Cook-to-order: the chef makes your dish fresh when you order. Always current, but you wait, and the kitchen works on every order.SSR, the server renders HTML on each request. Fresh data, slower first byte, work per visit.
Pre-made on the shelf: cooked once this morning, grabbed instantly. Lightning fast, but it's as fresh as this morning.SSG, HTML built once at deploy time, served from a CDN. Instant, but stale until you rebuild.
Meal-prep that refreshes on a timer: a batch sits ready, and a fresh batch is quietly made every hour.ISR, static pages that regenerate in the background on an interval. Fast like static, fresher than static.
Cook it yourself at the table: the kitchen hands you raw ingredients and a stove.CSR, the browser receives an empty shell plus JS and assembles the page itself.
Same meal, three kitchens, that's the entire rendering debate.

Where the HTML actually comes from

Every strategy answers the same request differently. Here's the same page request flowing to four different producers, then arriving at the browser where, for the JS-driven ones, it gets hydrated into a live app.

ship shell + JSrender nowserve prebuiltattach handlers
Page request

user hits a URL

Browser (CSR)

builds HTML from JS

Server (SSR)

renders per request

Build / CDN (SSG·ISR)

rendered ahead of time

HTML to browser

first paint

Hydrate

JS attaches, page interactive

One request, four places the HTML can be built, then hydration wires it up in the browser.

  1. 1

    The request arrives

    A user clicks a link or types a URL. The same starting point for all four strategies, what differs is who answers.

  2. 2

    Someone produces HTML

    CSR ships a near-empty shell and lets the browser build the DOM from JS. SSR runs your components on the server right now. SSG/ISR hand back HTML that was rendered earlier, at build time, or refreshed in the background.

  3. 3

    First paint happens

    The moment usable HTML hits the screen. For SSR/SSG/ISR this is fast and content-rich. For CSR it waits until the JS bundle downloads and runs.

  4. 4

    Hydration makes it interactive

    For any JS framework, the server-rendered HTML is static until the client JS loads, walks the DOM, and attaches event handlers. Until hydration finishes, buttons look ready but don't respond.

The four strategies, side by side

This table is the one you'll come back to. Read it as four levers, first paint, SEO, server cost, and freshness, that you can't all max at once.

First paintSEOServer costData freshness
CSRSlow (waits for JS)Weak by defaultCheapest (static host)Live (fetches on load)
SSRFast (HTML ready)StrongHighest (work per request)Live (per request)
SSGFastest (CDN)StrongNear zero (just files)Stale until rebuild
ISRFastest (CDN)StrongLow (occasional regen)Near-fresh (timed/on-demand)
The core trade-off: nobody wins every column.

A useful read: SSG and ISR look identical to the visitor, both serve a prebuilt file from a CDN. The difference is purely on your side: SSG only updates when you redeploy, while ISR refreshes itself on a schedule or when triggered. SSR is the only one paying compute on every single visit, which is exactly why "just SSR everything" gets expensive.

What this looks like in config

You rarely hand-build these. In a framework like Next.js, the strategy is often one line, whether a fetch is cached, and whether it revalidates. The mental model maps straight onto the config.

app/products/[id]/page.tsx
tsx
// SSG, fetched once at build, served forever (until next deploy)
async function getStatic(id: string) {
  const res = await fetch(`/api/products/${id}`, { cache: "force-cache" });
  return res.json();
}

// ISR, static, but regenerate in the background at most every 60s
async function getIsr(id: string) {
  const res = await fetch(`/api/products/${id}`, { next: { revalidate: 60 } });
  return res.json();
}

// SSR, render fresh on every request, never cache
async function getSsr(id: string) {
  const res = await fetch(`/api/products/${id}`, { cache: "no-store" });
  return res.json();
}

Pro tip

Notice there's no CSR line here, CSR is what you get when you fetch **inside a client component with `useEffect`** instead of in a server-rendered function. The choice of *where you fetch* quietly decides your rendering strategy.

Hydration: the part everyone underestimates

Server-rendered HTML (SSR, SSG, ISR) reaches the browser as dead markup, it looks right, but no click does anything yet. Hydration is the process where the client framework downloads, re-runs your components, walks the existing DOM, and attaches event listeners so the page becomes interactive.

This creates a sneaky gap. Your content paints fast (great!), but there's a window where the page looks interactive and isn't. A user taps a menu, nothing happens, they tap again, and now two clicks fire once hydration lands. The bigger your JS bundle, the longer that dead window, which is why "server-rendered" alone doesn't guarantee a fast-feeling app.

The modern answer is to hydrate less. React Server Components and similar approaches split your tree: components that are purely presentational render on the server and ship as plain HTML with *zero* client JS, while only the genuinely interactive bits (a search box, a cart button) ship and hydrate. This hybrid model keeps the fast first paint of static rendering while shrinking the hydration cost that used to come bundled with it.

  • Server Components, run on the server, send HTML, ship no JS to the client. Use for layout, content, data display.
  • Client Components, ship JS and hydrate. Use only where you need state, effects, or event handlers.
  • The win, you pay hydration cost only for the interactive islands, not the whole page.

Common mistakes that cost you users

  1. CSR for a content or marketing site. Blogs, docs, landing pages, e-commerce listings, anything that lives or dies by search traffic, should never be pure client-rendered. Crawlers and social previews see an empty shell, and your first paint is gated on a bundle. Pick SSG or ISR.
  2. Ignoring hydration cost. Shipping a 500 KB bundle and assuming SSR "made it fast", the HTML paints quickly but the page is dead until hydration finishes. Measure interactivity (INP / Total Blocking Time), not just first paint, and cut the bundle.
  3. SSR-ing everything. Rendering every page per request when 90% of it is identical for every user burns server compute and adds latency you didn't need. If the data doesn't change per request, it should be SSG or ISR, not SSR.
  4. Forgetting freshness with SSG. Pure static means stale until redeploy. If prices, inventory, or article edits need to show up within minutes, reach for ISR's revalidate instead of triggering a full rebuild for every change.
  5. Treating it as one global setting. Modern frameworks let you choose per route. Your marketing pages can be SSG, your dashboard SSR, your product pages ISR, in the same app. Don't pick one strategy for everything.

Takeaways

The whole article in seven lines

  • A rendering strategy is just **where and when your HTML is built**.
  • **CSR** builds in the browser, cheap to host, weak SEO, slow first paint.
  • **SSR** builds on the server per request, fresh and SEO-strong, but pays compute every visit.
  • **SSG** builds once at deploy, fastest and cheapest, but stale until you rebuild.
  • **ISR** is SSG that refreshes itself on a timer, static speed, near-fresh data.
  • **Hydration** is the gap between "looks ready" and "actually responds", measure it, shrink the bundle.
  • The modern hybrid (Server Components) ships HTML for most of the page and hydrates only the interactive islands. Choose **per route**, not per app.

Where to go next

Rendering strategy decides *where* the HTML is built, but to reason about why first paint and hydration feel the way they do, you need to understand what the browser does with that HTML once it arrives, and how to measure the result.

Want to go deeper?

This article covers concepts taught hands-on in the Cloud Engineer and DevOps career paths, with real terminal labs, production scenarios, and structured lessons.