Compare client-side rendering, server-side rendering, static generation and incremental regeneration.
Why it matters
Where and when your HTML is produced shapes almost everything users feel: how soon they see content, how search engines index the page, how fresh the data is, and how much your servers cost. Modern frameworks such as Next.js, Remix, Nuxt, SvelteKit and Astro let you choose per page, so a mid-level developer is expected to pick the right strategy and explain the trade-off.
The four strategies
Client-side rendering (CSR). The server sends an almost empty HTML shell and a JavaScript bundle. The browser downloads and runs the bundle, fetches data, then renders. A default Vite React app works this way.
- Good for: logged-in dashboards and tools where SEO does not matter and the user stays a long time.
- Costs: nothing useful appears until the JavaScript runs, so LCP suffers on slow devices; crawlers and link previews see an empty page.
Server-side rendering (SSR). On each request the server runs your components, fetches data, and sends finished HTML. The browser shows it immediately, then hydrates: it downloads the same JavaScript and attaches event handlers so the page becomes interactive.
- Good for: pages that are personalised or must always be fresh, such as search results or a signed-in home page.
- Costs: a server does work on every request (time to first byte depends on your slowest data call), and the page is visible before it is interactive.
Static site generation (SSG). HTML is produced once at build time and served from a CDN.
- Good for: content that is the same for everyone and changes rarely: docs, blog posts, marketing pages, a course catalog.
- Costs: data is as old as the last build; thousands of pages make builds slow.
Incremental static regeneration (ISR) (Next.js name; other frameworks call similar features stale-while-revalidate caching). Pages are static, but the framework regenerates a page in the background after a set time or when you trigger revalidation. The first visitor after the window gets the cached page while a fresh one is built.
- Good for: large catalogs that change occasionally, such as product or course pages.
- Costs: some visitors see slightly stale data; you need a server or platform that supports it.
Side by side
| When HTML is made | Data freshness | First content | Server cost per view | |
|---|---|---|---|---|
| CSR | In the browser | Live (fetched by the client) | Slowest | Lowest (static shell) |
| SSR | Per request | Live | Fast | Highest |
| SSG | At build | As of last build | Fastest (CDN) | Lowest |
| ISR | At build, then in the background | Within the revalidate window | Fastest | Low |
Hydration and its cost
SSR and SSG pages still ship the component JavaScript so the browser can hydrate. Until hydration finishes, buttons may not respond, which can hurt INP. Ways frameworks reduce this:
- Streaming SSR sends HTML in chunks so the shell appears before slow data is ready.
- React Server Components (Next.js App Router) render some components only on the server, so their code never ships to the browser.
- Islands (Astro) hydrate only the interactive widgets on an otherwise static page.
Mixing strategies: a worked example
Coding Help-style learning platform, built with Next.js:
/coursesand/courses/[slug](public catalog): SSG + ISR with a revalidate window, because the same content is shown to everyone, it must be indexable, and admins edit it occasionally./dashboard(signed in, personal progress): CSR or SSR. It is behind login so SEO does not matter; CSR with a skeleton keeps the server simple, SSR avoids the loading flash./search?q=...: SSR, because results depend on the query and should be shareable and indexable./terms: plain SSG; it changes a few times a year.
In the Next.js pages router this looks like:
// pages/courses/[slug].tsx
export async function getStaticProps({ params }) {
const course = await loadCourse(params.slug);
if (!course) return { notFound: true, revalidate: 60 };
return { props: { course }, revalidate: 300 }; // rebuild at most every 5 minutes
}
export async function getStaticPaths() {
return { paths: [], fallback: "blocking" }; // build pages on first request
}
How to choose
- Is the page the same for every visitor? If yes, start with SSG, adding ISR if it changes.
- Is it personalised or does it need to be fresh on every request, and should it be indexable? Use SSR.
- Is it an interactive app behind login? CSR is fine; SSR is optional.
- Within any page, client-fetch the small personalised parts (a "Continue learning" button) instead of making the whole page dynamic.
Common mistakes
- SSR for everything "for SEO", paying server time on pages that could be static.
- CSR for public content that needs search and link previews.
- Reading cookies or user data in a static page, which then caches one user's data for everyone. Personalised data must come from SSR or a client fetch.
- Hydration mismatches: rendering
Date.now()or random values on the server produces different HTML from the client and triggers warnings. - Ignoring the fallback state for ISR pages that are built on first request.
Quick recap
- CSR renders in the browser; SSR per request; SSG at build; ISR is static with background refresh.
- Static is fastest and cheapest; SSR gives freshness and personalisation; CSR suits private apps.
- Hydration makes server-rendered pages interactive and has a JavaScript cost; streaming, server components and islands reduce it.
- Mix strategies per route, and never bake personal data into a cached page.
Try it
Open the Do tab and choose strategies for several real pages.