Go Back

Varun Narayanan

April 20, 2026

SSR vs ISR vs SSG vs CSR - Rendering Patterns Explained

A comprehensive guide to modern web rendering strategies - Server-Side Rendering, Incremental Static Regeneration, Static Site Generation, and Client-Side Rendering with real Next.js App Router examples.

SSR vs ISR vs SSG vs CSR - Rendering Patterns Explained

SSR vs ISR vs SSG vs CSR

When you're building a modern web app, one of the first real architectural decisions you face isn't about which database to pick or how to structure your folders — it's how your pages get rendered.

Get it wrong and you'll end up with a blazing-fast site that Google can't crawl, or a perfectly SEO'd page that tanks under load. This guide breaks down the four major rendering patterns in Next.js App Router, with real code and honest trade-offs.


A Quick Note on the App Router

Before diving in: Next.js App Router (introduced in Next.js 13+) treats all components as React Server Components by default. That means they run on the server and their JavaScript is never shipped to the browser — which is great for performance.

The four rendering strategies below describe when and where the HTML is generated, and they're all fully supported in the App Router.


1. Client-Side Rendering (CSR)

With CSR, the server sends a nearly empty HTML shell along with a JavaScript bundle. The browser downloads and runs that JS, then renders the content — meaning the user sees a blank or loading screen until JavaScript finishes executing.

React

1

'use client';

2

 

3

import { useState, useEffect } from 'react';

4

 

5

export default function CSRPage() {

6

const [data, setData] = useState(null);

7

 

8

useEffect(() => {

9

fetch('/api/data')

10

.then((res) => res.json())

11

.then((data) => setData(data));

12

}, []);

13

 

14

if (!data) return <div>Loading...</div>;

15

 

16

return <div>{data.title}</div>;

17

}

Note: In App Router, even 'use client' components are pre-rendered on the server as a static shell on first load. What makes this "CSR" is that the data fetching happens on the client side via useEffect.

Pros:

  • Smooth, SPA-like navigation after initial load
  • Reduces server load since there's no per-request HTML generation
  • Great for highly interactive UIs

Cons:

  • Poor SEO — search engines see an empty page initially
  • Slower time to content, especially on low-end devices
  • Requires a loading state for every data fetch

Best for: Dashboards, gated content behind authentication, real-time apps where SEO doesn't matter.


2. Server-Side Rendering (SSR)

With SSR, the server generates the full HTML on every single request. By the time the browser receives the response, the page is already populated with data. In App Router, you opt into this with cache: 'no-store' on your fetches or by exporting dynamic = 'force-dynamic'.

React

1

// app/feed/page.tsx

2

export const dynamic = 'force-dynamic';

3

 

4

async function getData() {

5

const res = await fetch('https://api.example.com/feed', {

6

cache: 'no-store',

7

});

8

return res.json();

9

}

10

 

11

export default async function FeedPage() {

12

const data = await getData();

13

 

14

return (

15

<ul>

16

{data.items.map((item) => (

17

<li key={item.id}>{item.title}</li>

18

))}

19

</ul>

20

);

21

}

Pros:

  • Excellent SEO — fully rendered HTML on every request
  • Always shows fresh, up-to-date data
  • Works great for personalized or user-specific content

Cons:

  • Higher server cost — every request triggers a full render
  • Higher TTFB (Time to First Byte) compared to static pages
  • Doesn't scale as cheaply as static strategies

Best for: User feeds, personalized dashboards, pages that need real-time data on every load.


3. Static Site Generation (SSG)

SSG generates the HTML exactly once — at build time. The output is a set of static files that can be served instantly from a CDN anywhere in the world. No server processing happens on each request.

React

1

// app/about/page.tsx

2

 

3

async function getContent() {

4

const res = await fetch('https://api.example.com/about', {

5

cache: 'force-cache',

6

});

7

return res.json();

8

}

9

 

10

export default async function AboutPage() {

11

const content = await getContent();

12

 

13

return <div>{content.description}</div>;

14

}

Tip: If your page has no dynamic data at all, just skip the fetch entirely — Next.js will statically render it automatically.

React

1

// Fully static — no data needed

2

export default function PricingPage() {

3

return <div>Our pricing plans...</div>;

4

}

Pros:

  • Fastest possible load times — served straight from a CDN
  • Incredibly scalable and cheap to host
  • Perfect for SEO

Cons:

  • Content becomes stale the moment it's deployed
  • Requires a full rebuild and redeploy to update content
  • Not suitable for frequently changing or personalized data

Best for: Marketing pages, documentation, pricing pages, landing pages — anything that rarely changes.


4. Incremental Static Regeneration (ISR)

ISR is the sweet spot between SSG and SSR. Pages are statically generated at build time, but Next.js quietly regenerates them in the background at a set interval — without requiring a full redeploy.

React

1

// app/blog/[slug]/page.tsx

2

export const revalidate = 60; // Regenerate at most every 60 seconds

3

 

4

async function getPost(slug: string) {

5

const res = await fetch(`https://api.example.com/posts/${slug}`);

6

return res.json();

7

}

8

 

9

export default async function BlogPostPage({

10

params,

11

}: {

12

params: { slug: string };

13

}) {

14

const post = await getPost(params.slug);

15

 

16

return (

17

<article>

18

<h1>{post.title}</h1>

19

<p>{post.body}</p>

20

</article>

21

);

22

}

Here's what actually happens under the hood with revalidate = 60:

  1. The first visitor gets the statically generated page instantly
  2. For the next 60 seconds, everyone gets that same cached version
  3. After 60 seconds, the next visitor still gets the cached page — but Next.js triggers a background regeneration
  4. Once regeneration is done, subsequent visitors get the updated page

This is the stale-while-revalidate pattern. Nobody waits for a rebuild — everyone always gets a served response immediately.

On-Demand Revalidation

You can also trigger a revalidation manually via an API route, which is useful for CMS webhooks:

TypeScript

1

// app/api/revalidate/route.ts

2

import { revalidatePath } from 'next/cache';

3

import { NextRequest } from 'next/server';

4

 

5

export async function POST(req: NextRequest) {

6

const { path } = await req.json();

7

revalidatePath(path);

8

return Response.json({ revalidated: true });

9

}

Pros:

  • Fast like SSG since it's served from cache
  • Fresh like SSR thanks to background updates
  • On-demand revalidation for instant updates when needed

Cons:

  • The first user after a cache expiry might briefly see stale content
  • Slightly more complex to reason about than pure SSG or SSR

Best for: Blogs, product catalogs, news sites, e-commerce listings — content that updates regularly but not in real-time.


Comparison at a Glance

PatternHTML GeneratedSEOSpeedData Freshness
CSRRuntime (Browser)LowSlow (Initial)Always fresh
SSRRuntime (Server)HighMediumAlways fresh
SSGBuild TimeHighFastestStale until rebuild
ISRBuild + BackgroundHighFastEventually fresh

Which One Should You Use?

Most production apps use all four — just on different routes. Here's a simple mental model:

  • Use SSG for content that barely changes — marketing pages, docs, about pages. Deploy once, serve forever.
  • Use ISR for content that updates occasionally — blogs, product pages, news articles. Best of both worlds.
  • Use SSR for content that must be fresh on every request — user feeds, personalized data, live inventory.
  • Use CSR for content behind authentication, or heavy interactive UIs where SEO doesn't matter.

A well-architected Next.js app might use SSG for the homepage, ISR for blog posts, SSR for the dashboard, and CSR for a real-time notification panel — all in the same project. The key insight is that rendering strategy is a per-route decision, not a whole-app one. Next.js makes it easy to mix and match, so take advantage of that.

Happy shipping!

— Varun Narayanan