A compilation of my understanding on different frontend rendering strategies as part of my interview prep

A A A A A

Rendering strategies, explained

Every web application makes a choice about where and when its HTML gets built. That choice has downstream effects on performance, SEO, infrastructure cost, and how fresh your data can be.

Most developers should be familiar at some point of their career with the four horsemen of the rendering acronyms, but once in a while (and more frequent than not) the nuances and trade-offs escaped us and only click once you see the actual request flows side by side.


The core question

Before we get into each strategy, let’s us ourselves: What problem are we actually solving?

When a user visits a URL, their browser needs HTML to display. The question every rendering strategy answers is: who builds that HTML, and when?

  • CSR: The browser builds it, on every visit
  • SSR: The server builds it, on every request
  • SSG: A build process builds it, once at deploy time
  • ISR: A build process builds it, then quietly rebuilds specific pages in the background
  • Islands: A build process builds it statically, then only the interactive parts get JavaScript

That’s the whole map. Everything else is trade-offs.


Client-Side Rendering (CSR)

The server sends an almost empty HTML file (usually called index.html) e.g. a <div id="app"></div> and a <script> tag. The browser then downloads the JavaScript bundle, runs it, and your framework builds the page from scratch in the browser.

src/App.vue
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld />
</template>
src/main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'

createApp(App).mount('#app')

Nothing is visible until the JS finishes, hence the first navigation will be slow. Every navigation after the first is fast because it’s just JavaScript swapping components with no server round-trips needed.

Client-Side Rendering (CSR) Browser requests the page, server returns an empty HTML shell plus a JS bundle from CDN, browser runs the JS and builds the entire page itself. Browser runs JS, builds DOM Server empty shell + JS bundle CDN JS bundle, assets GET /page empty HTML JS bundle blank JS loading interactive Rendering happens entirely in the browser. Server does no HTML generation at runtime.

When to use it: Internal dashboards, admin tools, SaaS apps behind a login. Anywhere SEO doesn’t matter and the UI is deeply interactive.

The gotcha: That initial blank screen. On slow connections or low-end devices, users stare at nothing while the JS bundle downloads and executes. This tanks your LCP.


Server-Side Rendering (SSR)

For every request, the server runs your framework, fetches data, renders the full HTML, and ships it to the browser. The user sees real content almost immediately, before any JavaScript runs.

After the HTML arrives, the JS bundle still loads and hydrates the page, attaching event listeners and reactive state. So you can read the content before JS is ready, just that you can’t interact with it yet.

Server-Side Rendering (SSR) Browser sends GET request. Server fetches data from the database, runs the framework, renders complete HTML, and returns it. Browser displays content immediately. JS then hydrates the page to make it interactive. Browser full HTML, readable now JS hydrates after Server 1. receive request 2. fetch data 3. render full HTML on every request Database / API live, per-user data GET /page full HTML page wait readable JS hydrates interactive Server renders HTML on every request — great SEO and personalisation, higher infrastructure cost.

When to use it: E-commerce product pages, news sites, anything with SEO requirements and dynamic or personalised data. If content changes per-user or per-request, SSR is your only real option among server-rendered strategies.

The gotcha: You’re paying for server compute on every single request. Under heavy load this gets expensive real fast. Plus, you still need to handle hydration on the client side.


Static Site Generation (SSG)

At build time i.e. before any user visits, your framework fetches all the data it needs and renders everything for every page into static HTML files. Those files are deployed to a CDN. When someone requests a page, the CDN just serves the file. No server renders anything at runtime.

Static Site Generation (SSG) At build time the build process fetches all data and renders every page to static HTML files, then deploys them to a CDN. At request time, the browser asks the CDN for a page and receives the pre-built file instantly with no server involved. Build time (once at deploy) Request time (every visit) Build process fetch all data render all pages deploy CDN pre-built files globally cached GET /page pre-built file, instantly Browser no server render instant display visible and interactive immediately Content is frozen at the last build. Best for blogs, docs, marketing pages. Not suitable for per-user data.

When to use it: Blogs (like this one), documentation, marketing and landing pages, portfolios. Anything where the content is the same for every visitor and doesn’t change by the minute.

The gotcha: Content is frozen at the last build. If your CMS article updates, visitors see the old version until you redeploy. And if you have thousands of pages, build time scales linearly.


Incremental Static Regeneration (ISR)

What if I want SSG but with occasional content updates?

ISR is basically SSG with a safety valve. Pages are still pre-built and served from a CDN, but you set a time to live (TTL). After that window expires, the next request triggers a background rebuild of just that page. The current visitor still gets the stale cached page immediately but the next visitor gets the freshly rebuilt one.

Incremental Static Regeneration (ISR) CDN serves a cached pre-built page instantly. When the TTL expires, the next request triggers a background rebuild of just that page. The current visitor gets the cached version unaffected. The next visitor receives the freshly rebuilt page, also instantly. CDN cache stores page.html TTL: 60 seconds Current visitor gets page instantly may be up to 60s old GET /page Next visitor gets fresh page also instantly When TTL expires, next request triggers a silent background rebuild of only that page: Background regen fetch data → re-render → update CDN cache Static performance + reasonably fresh content. Cannot personalise per user or serve real-time data.

When to use it: Product pages where prices or stock change periodically but not every second. News articles. Any large site where a full rebuild on every content change is impractical.

The gotcha: Stale content is still possible within the TTL window. And like SSG, you can’t personalise per user.


WTF is Hydration?

Not sure about you but hydration is one of those tech jargons that gets thrown around a lot but I almost always find myself having a hard time grasping it.

For a start, hydration isn’t a rendering strategy, but it comes up whenever you use SSR or SSG. When the server sends HTML to the browser, the page looks correct but is static i.e. no event listeners, no reactivity. The JavaScript framework then loads and hydrates the page: it walks the existing DOM, matches it against the component tree it would have built, and attaches all the event handlers and reactive state without tearing down and rebuilding the HTML from scratch.

Hydration — making server-rendered HTML interactive Three stages: server HTML arrives visible but static, the JS framework loads and walks the existing DOM attaching listeners and reactivity, the page becomes fully interactive. A warning box below explains what a hydration mismatch is and its common causes. HTML arrives visible, readable no listeners, not interactive JS loads Framework hydrates walks existing DOM attaches listeners + reactivity Fully interactive clicks work, reactive no layout flash The framework reuses the server HTML without tearing it down — this prevents a visible flash on load. Hydration mismatch Server HTML differs from what the client renders — framework re-renders from scratch, causing a flash. Causes: Date.now() in render, accessing window outside onMounted, client-only conditional content.

Why mismatches happen

If you’re a frontend developer, chances are you might encounter hydration mismatch warning from your console that looks like this:

Vue hydration mismatch warning

A mismatch occurs when the HTML the server sent doesn’t match what the client-side framework would render on its first run. The framework falls back to a full client-side re-render — which defeats the point of SSR entirely, and often causes a visible layout flash.

The usual culprits:

  • Non-deterministic values in renderDate.now(), Math.random(). They return different values on the server and on the client.
  • Browser-only APIs accessed during SSRwindow, localStorage, document don’t exist in Node.js. Access these inside onMounted, which only runs client-side.
  • Content that differs by environment — anything conditionally rendered based on viewport size, browser locale, or client state that doesn’t exist at SSR time.

In Vue/Nuxt, wrapping browser-only content in <ClientOnly> sidesteps the issue entirely.


Partial hydration & islands architecture

Traditional hydration has a problem: it hydrates everything. Even if 90% of your page is static content that will never need an event listener, the framework still loads and walks the entire component tree. That’s a lot of JavaScript downloaded and executed for nothing.

The islands architecture flips this. Instead of hydrating the whole page and carving out static parts, you start from zero JavaScript and opt in to interactivity only where you need it. Think of your page as an ocean of static HTML with small, independent “islands” of interactivity scattered across it — each hydrating on its own, in isolation.

This is exactly how Astro works (and how this site is built), and it’s funny that I just found out about this. Because this is a newly-discovered knowledge, I extracted it into a TIL post which you can read here.


The decision map

SituationStrategyWhy
Internal dashboard / admin toolCSRNo SEO, deeply interactive, behind auth
Marketing or landing pageSSGContent is static, SEO critical, must be fast
Blog or documentation siteSSGArticles rarely change, zero server cost
E-commerce product pagesISR or SSRSEO + reasonably fresh stock/price data
News siteISRArticles update but stale-for-60s is fine
User profile / personalised pageSSR or CSRPer-user data can’t be pre-built
Live data (prices, scores)SSR + client pollingMust be truly real-time

One last thing worth internalising: These strategies aren’t mutually exclusive. Most mature applications mix them and most modern meta-frameworks like Nuxt support this use case which is known as hybrid rendering.

In other words, a SaaS product might use SSG for its marketing site, SSR for its SEO-critical public pages, and CSR for the authenticated dashboard — all in the same codebase, handled by the same framework.

Comparing all four

Rendering strategies — comparison table CSR, SSR, SSG, and ISR compared across first load, SEO, dynamic data, and server cost with colour-coded ratings. Strategy Where rendered When rendered First load SEO Dynamic data Server cost CSR Browser On every visit Slow Poor Full Minimal SSR Server On every request Fast Great Full High SSG Build server At build time Fastest Great None Zero ISR Build + background Build + on TTL expiry Fast Great Partial Low

Conclusion

At the end of the day, the choice isn’t philosophical. It’s merely a function of:

  1. Does this page need SEO?
  2. How fresh does the data need to be, and;
  3. How much server compute am I willing to pay for?

As the Malay adage goes, tepuk dada, tanya selera.