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.
.webp&w=3840&q=75)
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.
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 viauseEffect.
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'.
1
// app/feed/page.tsx2
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.
1
// app/about/page.tsx2
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.
1
// Fully static — no data needed2
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.
1
// app/blog/[slug]/page.tsx2
export const revalidate = 60; // Regenerate at most every 60 seconds3
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:
- The first visitor gets the statically generated page instantly
- For the next 60 seconds, everyone gets that same cached version
- After 60 seconds, the next visitor still gets the cached page — but Next.js triggers a background regeneration
- 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:
1
// app/api/revalidate/route.ts2
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
| Pattern | HTML Generated | SEO | Speed | Data Freshness |
|---|---|---|---|---|
| CSR | Runtime (Browser) | Low | Slow (Initial) | Always fresh |
| SSR | Runtime (Server) | High | Medium | Always fresh |
| SSG | Build Time | High | Fastest | Stale until rebuild |
| ISR | Build + Background | High | Fast | Eventually 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