ForgeFrontend — Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

CSR vs SSR vs SSG: Rendering Strategies

Short answer

The three differ in where and when HTML is produced. CSR builds it in the browser after JavaScript loads. SSR builds it on the server on every request. SSG builds it once at deploy time and serves the same file to everyone. Everything else — first paint, SEO, cost, freshness — follows from that choice.

CSRSSRSSG
HTML builtIn the browserOn the server, per requestAt build time, once
Initial HTMLAn empty shellCompleteComplete
Time to first byteFastSlower — waits for dataFastest — from a CDN
First contentful paintAfter JS loads and runsImmediateImmediate
SEODepends on the crawlerStrongStrong
Data freshnessLive on every viewLive on every requestAs of the last build
Server cost per viewNoneReal — compute per requestNone — a static file
Best forDashboards behind a loginPersonalised, SEO-critical pagesDocs, blogs, marketing, catalogues
Fails whenContent must be indexedTraffic spikesContent changes constantly
One decision — where the HTML is built — with everything that follows.

Where the HTML comes from

Strip away the acronyms and there is one question: at the moment the browser asks for a URL, does a complete HTML document already exist? With SSG it was written to disk at build time. With SSR it is generated right then, for that request. With CSR it does not exist at all — the server sends a near-empty page plus a script bundle, and React builds the document in the browser.

What actually comes over the wirehtml
1<!-- CSR: view-source on a Create React App page -->
2<div id="root"></div>
3<script src="/static/js/bundle.js"></script>
4<!-- → nothing to read, nothing to index, nothing to paint
5       until the bundle downloads, parses and executes -->
6
7<!-- SSR / SSG: the same page, rendered on the server -->
8<div id="root">
9  <h1>React Interview Kit</h1>
10  <p>40 questions with model answers…</p>
11</div>
12<script src="/static/js/bundle.js"></script>
13<!-- → readable, paintable and indexable before any JS runs.
14       The bundle then hydrates it to make it interactive. -->

Note that the script tag appears in both. This is the point people miss: SSR and SSG do not remove the JavaScript. They change what the user sees while it is loading. The bundle still downloads, still runs, and still has to attach event handlers to the markup that came from the server.

What the user actually experiences

The strategies trade against each other on a timeline, and putting real metric names on that timeline is what turns a definition into an argument. Time to first byte measures how long the server took. First contentful paint is when anything appears. Largest contentful paint is when the main content appears — a Core Web Vital. Time to interactive is when clicks start working.

MetricCSRSSRSSG
TTFBFast — the shell is trivialSlow — data fetch happens firstFastest — CDN edge
FCPLate — needs the JS bundleEarlyEarliest
LCPLate — often after a second fetchEarlyEarliest
TTIAfter hydrationAfter hydrationAfter hydration
Subsequent navigationsFastest — client-side routingFast, with a server round tripFast, prefetched
Behaviour with JS disabledBlank pageContent rendersContent renders
The same page, three strategies, on a typical mobile connection.

Two things stand out. First, TTI is the same in all three, because interactivity always waits for hydration — server rendering buys you a faster paint, not a faster click. Second, CSR is not universally slower: once loaded, its client-side navigations avoid the server entirely, which is why app-like products with long sessions still choose it.

Client-side rendering

CSR is the default of a plain React or Vite app. The server's job is to hand over static assets; everything else happens in the browser. That is a genuinely good fit for software people log into and use for a long time — an admin panel, an editor, an internal tool — where the first load happens once and the content is private anyway.

The CSR waterfall, and why it is slow to first paintjsx
1// index.html → bundle.js → React mounts → useEffect fires → fetch → render
2function Dashboard() {
3  const [data, setData] = useState(null);
4
5  useEffect(() => { fetch("/api/stats").then(r => r.json()).then(setData); }, []);
6
7  if (!data) return <Spinner />;
8  return <Charts data={data} />;
9}
10// → the request for /api/stats cannot start until the bundle has
11//   downloaded, parsed and run. On 4G that is often 2-3 seconds
12//   of spinner before the network call even begins.

Server-side rendering

SSR runs your components on the server for each request, producing complete HTML that the browser can paint immediately. It is the right answer when a page must be indexable and its content is personalised or genuinely live — a logged-in feed, a search results page, a dashboard with an SEO-visible summary.

The cost is on the server. Every request does work, which means compute you pay for, latency you must control, and a failure mode where a traffic spike or a slow database query degrades every visitor rather than just one. It also moves your data-fetching latency into TTFB, so an unindexed query on the server makes the whole page feel slow rather than just one widget.

SSR in the Next.js App Routerjsx
1// app/feed/page.tsx — a Server Component that opts out of caching
2export const dynamic = "force-dynamic";
3
4export default async function Feed() {
5  const posts = await db.post.findMany({ take: 20 });
6  return <PostList posts={posts} />;
7}
8// → runs on every request. The HTML arrives complete, and the
9//   database query time is added to TTFB for every visitor.

This is 1 of 200+ questions in the Complete Frontend Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the Complete Frontend Kit → ₹499

Static generation and ISR

SSG renders every page once, at build time, and the result is a file. A CDN serves it from an edge location near the user, which is as fast as the web gets and costs essentially nothing per view. It is unbeatable for content that does not change per visitor: documentation, blog posts, marketing pages, landing pages, product catalogues.

The historical objection was staleness — new content required a full rebuild, which does not scale to ten thousand pages. Incremental Static Regeneration removes that. Pages are served static, and after a revalidation window the next request triggers a background rebuild of that one page while still serving the old version instantly.

Static, then ISR, in the same file shapejsx
1// app/kits/[slug]/page.tsx
2export async function generateStaticParams() {
3  const kits = await getKits();
4  return kits.map(k => ({ slug: k.slug }));
5}
6// → these pages are built at deploy time
7
8export const revalidate = 3600;
9
10export default async function Kit({ params }) {
11  const kit = await getKit(params.slug);
12  return <KitPage kit={kit} />;
13}
14// → served from the CDN. One hour after a page was generated, the
15//   next visitor still gets the cached copy instantly while a fresh
16//   one is built in the background for everyone after them.

Hydration: the part that is easy to skip

Server-rendered HTML is not interactive. React has to load in the browser, walk the existing DOM, and attach its event handlers and internal state to it — that is hydration. Until it finishes, the page looks ready and does nothing, which is a worse experience in some ways than an honest spinner, because users click and nothing happens.

Hydration also demands that the server's markup and the client's first render match exactly. When they do not — because you rendered a Date, a random value, or something read from localStorage — React logs a hydration mismatch and discards the server HTML for that subtree, throwing away the benefit you paid for.

The classic hydration mismatchjsx
1function Greeting() {
2  return <p>Rendered at {new Date().toLocaleTimeString()}</p>;
3}
4// → Warning: Text content did not match.
5//   Server: "10:42:01" Client: "10:42:04"
6
7// Fix: render the deterministic version, fill in on the client
8function Greeting() {
9  const [time, setTime] = useState(null);
10  useEffect(() => setTime(new Date().toLocaleTimeString()), []);
11  return <p>Rendered at {time ?? "…"}</p>;
12  // → server and client both produce "…" on the first pass,
13  //   so hydration matches and the value appears after mount
14}

React Server Components, the fourth option

RSC changes the terms of the debate rather than adding a variant. A Server Component runs only on the server, and its JavaScript is never sent to the browser at all — what ships is the rendered output. So a page can be server-rendered without paying the hydration cost for the parts that are not interactive, and the client bundle shrinks to just the interactive islands.

Server by default, client where neededjsx
1// app/kits/page.tsx — a Server Component, no "use client"
2import AddToCart from "./add-to-cart";
3
4export default async function Kits() {
5  const kits = await db.kit.findMany();     // runs on the server
6  return kits.map(k => (
7    <article key={k.id}>
8      <h2>{k.title}</h2>
9      <AddToCart id={k.id} />               {/* the only client JS */}
10    </article>
11  ));
12}
13// → the markup and the database code never reach the browser;
14//   only add-to-cart.js is hydrated

Streaming completes the picture. With Suspense boundaries, the server sends the shell immediately and streams each slower section in as its data resolves, so a single slow query no longer holds up the entire page's TTFB. Together these mean the practical modern answer is rarely one strategy for a whole app — it is server rendering with static caching where possible, and client interactivity scoped to the components that need it.

Choosing, per route

The useful framing is that these are per-route decisions, not per-application ones. Next.js, Remix, Nuxt and SvelteKit all let a single project mix them, and a well-built product does. Work through it by asking who the content is for and how often it changes.

  1. Is the content public and does it need to be found in search? If not, CSR is on the table.
  2. Is it the same for every visitor? If yes, generate it statically.
  3. How often does it change? Rarely means plain SSG; regularly means ISR with revalidation on write.
  4. Is it personalised or genuinely live? That is SSR, or a server component rendered per request.
  5. Is the interactive part small? Keep it as a client island rather than making the page client-rendered.
  6. Would a slow data source block the whole page? Wrap it in Suspense and stream it.
PageStrategyWhy
Marketing home pageSSGIdentical for everyone, changes rarely
Blog or docs articleSSGSame, and there may be thousands of them
Product listingISRPublic and indexable, but stock and price move
Search resultsSSRUnique per query, still worth indexing
Logged-in dashboardCSR or RSCPrivate, so SEO is irrelevant
CheckoutCSRInteraction-heavy, never indexed
User profile page (public)ISR or SSRIndexable but user-specific and editable
Common surfaces and the strategy that usually wins.

What is hydration in React?

The process of attaching React to server-rendered HTML in the browser — walking the existing DOM, building the component tree, and wiring up event handlers and state without recreating the markup. Until it completes, the page is visible but not interactive.

What is ISR?

Incremental Static Regeneration: pages are static, but after a revalidation window the next request serves the cached copy instantly and triggers a background rebuild of that single page. It gives static delivery with periodic freshness and no full redeploy.

Is SSR always better for SEO than CSR?

It is more reliable. Googlebot does render JavaScript, but on a deferred pass, and many other crawlers and link-preview bots do not render at all. Server-rendered HTML removes the dependency entirely, which matters most for pages you need indexed quickly or shared on social platforms.

What is the difference between SSG and pre-rendering?

Pre-rendering is the umbrella term for producing HTML before the browser asks for it, which covers both SSG and SSR. SSG is the build-time variant specifically. Next.js documentation used pre-rendering as the general word for both, which is where the confusion comes from.

Do Server Components replace SSR?

No — they are a component model, not a rendering strategy, and they work alongside all of them. What they change is how much JavaScript reaches the browser: a Server Component's code never ships, so a server-rendered page can skip hydration for everything that is not interactive.

Frequently asked questions

What is the difference between CSR, SSR and SSG?
CSR builds the HTML in the browser after the JavaScript bundle loads. SSR builds it on the server for each request. SSG builds it once at deploy time and serves the same file to everyone. That one difference determines first paint, SEO reliability, data freshness and server cost.
Which is best for SEO?
SSG and SSR, because the content is in the HTML before any JavaScript runs, so every crawler and link-preview bot sees it. CSR relies on the crawler executing JavaScript, which Googlebot does on a deferred pass and many others do not do at all.
What is hydration?
Attaching React to HTML that was rendered on the server — building the component tree in the browser and wiring up event handlers to the existing DOM instead of recreating it. Before hydration completes the page looks finished but does not respond to clicks.
What is ISR in Next.js?
Incremental Static Regeneration. Pages are generated statically but carry a revalidation window; after it expires, the next request is still served the cached copy while a fresh version is built in the background. You can also trigger it on demand with revalidatePath or revalidateTag.
Is SSR slower than CSR?
SSR has a slower time to first byte, because the server fetches data before responding, but a much faster first contentful paint, because complete HTML arrives. CSR reverses that. For a first visit SSR almost always feels faster; for subsequent in-app navigation CSR usually wins.
When should I use client-side rendering?
For private, interaction-heavy software where SEO is irrelevant and sessions are long — admin panels, editors, dashboards behind a login, checkout flows. The one-time load cost is amortised, and client-side routing makes every navigation after that instant.
What causes a hydration mismatch error?
The server's HTML and the client's first render producing different output — typically from dates, random values, browser-only APIs like localStorage or window, or locale-dependent formatting. React discards the server markup for that subtree, which wastes the server rendering you paid for.
Can one app use all three strategies?
Yes, and good ones do. Next.js, Remix, Nuxt and SvelteKit all decide per route, so marketing pages can be static, search results server-rendered, and the dashboard client-rendered — all in one deployment.
What is the difference between SSG and ISR?
SSG builds every page at deploy time and they stay fixed until the next build. ISR builds them the same way but allows individual pages to be regenerated afterwards, either on a timer or on demand, without redeploying the whole site.
Do React Server Components replace SSR?
No. They are a component model that runs on the server and never ships its JavaScript to the browser, and they compose with server rendering rather than replacing it. Their benefit is a smaller client bundle and less hydration, not a different place to generate HTML.
What is streaming SSR?
Sending the HTML in chunks as it becomes ready instead of waiting for the whole page. With Suspense boundaries the shell arrives immediately and slower sections stream in as their data resolves, so one slow query no longer delays the entire response.
Which rendering strategy should I use for a blog?
SSG, with ISR if posts are edited after publishing. The content is identical for every visitor and changes rarely, so building once and serving from a CDN gives the best possible first paint at essentially zero per-view cost.

This is 1 of 200+ questions in the Complete Frontend Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the Complete Frontend Kit → ₹499
Written by Arun Karthikeyan · Last updated

Full kit

Complete Frontend Kit · ₹499

Get it →