SSR vs SSG vs ISR vs CSR
The Decision Framework
LinkedIn Hook
"Which rendering strategy would you use for this page?"
That question separates junior developers from senior engineers. And the answer is never just one word — it's a framework for thinking.
Most candidates fumble because they memorize definitions: "SSR renders on the server, SSG renders at build time." But interviewers don't want definitions. They want you to reason through trade-offs and explain why you'd pick one over another.
In Lesson 2.5, I built the decision framework I wish I'd had before my interviews — a comparison table, a flowchart, and ready-to-use answer templates. This is the capstone of Chapter 2, and it ties everything together.
Read the full lesson -> [link]
#NextJS #WebDevelopment #SSR #SSG #ISR #FrontendDevelopment #InterviewPrep
What You'll Learn
- A side-by-side comparison of SSR, SSG, ISR, and CSR across every dimension that matters
- A decision flowchart you can apply to any page in any project
- Interview answer templates using the "I would choose X because..." structure
- How real-world Next.js apps mix multiple strategies in one application
- The mental model that makes rendering strategy questions feel effortless
The Restaurant Analogy — Four Ways to Serve Food
Before we compare code and config, let's build intuition with an analogy. Imagine you're running a restaurant, and customers keep ordering the same dishes. You have four strategies for getting food to the table:
SSG (Static Site Generation) = Pre-made meals in the display case. You cook everything in the morning before the restaurant opens. When a customer orders, you grab a pre-made plate and serve it instantly. Blazing fast — but if the recipe changes, you have to remake everything from scratch. Best for dishes that don't change often.
ISR (Incremental Static Regeneration) = Pre-made meals with a refresh timer. Same as SSG, but you put a timer on each dish. Every 60 seconds (or whatever you set), a cook checks if the recipe changed and makes a fresh batch — but the current customers still get the existing plate while the new one is being prepared. You never close the restaurant to refresh the menu.
SSR (Server-Side Rendering) = Made-to-order in the kitchen. Every customer gets a freshly cooked meal. The waiter takes the order to the kitchen, the chef prepares it, and it comes out hot. Takes longer than grabbing from the display, but every plate is perfectly fresh and personalized to the customer's request.
CSR (Client-Side Rendering) = Meal kit delivery. You hand the customer raw ingredients and a recipe card. They cook it themselves at their table. The restaurant delivers fast (just hand over a bag), but the customer has to wait while they assemble and cook. Not great if a food critic (search engine) walks in — they see raw ingredients, not a finished dish.
+------------------------------------------------------------------+
| THE RESTAURANT MODEL |
+------------------------------------------------------------------+
| |
| SSG [ Pre-made ] --> Instant serve --> Stale if old |
| |
| ISR [ Pre-made ] --> Instant serve --> Background refresh |
| + timer |
| |
| SSR [ Made to ] --> Wait for chef --> Always fresh |
| order |
| |
| CSR [ Meal kit ] --> Customer cooks -> SEO blind spot |
| |
+------------------------------------------------------------------+
Napkin AI Visual Prompt: "Dark gradient (#0a0e1a -> #111827). Four horizontal lanes, each with a food metaphor icon on the left. SSG = display case (green #10b981), ISR = display case + clock (teal), SSR = chef cooking (purple #8b5cf6), CSR = meal kit box (orange). Arrows showing flow from order to delivery. White monospace labels. Title: 'Four Rendering Strategies'."
The Comprehensive Comparison Table
This is the table you need to have memorized. Every rendering question in an interview can be answered by referencing these dimensions.
+------------------+------------------+------------------+------------------+------------------+
| Dimension | SSG | ISR | SSR | CSR |
+------------------+------------------+------------------+------------------+------------------+
| When HTML is | Build time | Build time + | Every request | Client browser |
| generated | | background | | (after JS loads) |
| | | revalidation | | |
+------------------+------------------+------------------+------------------+------------------+
| Build time | Slow (generates | Same as SSG | Fast (no pages | Fast (no pages |
| impact | all pages) | initially | pre-rendered) | pre-rendered) |
+------------------+------------------+------------------+------------------+------------------+
| Request time | Instant (CDN) | Instant (cached) | Slower (server | Fast initial + |
| performance | | or fast regen | compute per req) | slow data fetch |
+------------------+------------------+------------------+------------------+------------------+
| Data freshness | Stale until | Stale within | Always fresh | Always fresh |
| | next build | revalidate | (real-time) | (client fetches) |
| | | window | | |
+------------------+------------------+------------------+------------------+------------------+
| SEO | Excellent | Excellent | Excellent | Poor (empty HTML |
| | (full HTML) | (full HTML) | (full HTML) | until JS runs) |
+------------------+------------------+------------------+------------------+------------------+
| Server load | Zero (static | Minimal | High (every | Zero (client |
| | files on CDN) | (occasional | request hits | does the work) |
| | | regen) | server) | |
+------------------+------------------+------------------+------------------+------------------+
| Personalization | None (same page | None (same | Full (cookies, | Full (after |
| | for everyone) | cached page) | headers, auth) | auth check) |
+------------------+------------------+------------------+------------------+------------------+
| CDN cacheable | Yes (perfect) | Yes (with TTL) | No (or complex | Yes (shell only) |
| | | | cache rules) | |
+------------------+------------------+------------------+------------------+------------------+
| Use when | Content rarely | Content changes | Content changes | Highly inter- |
| | changes | periodically | per user/request | active, no SEO |
+------------------+------------------+------------------+------------------+------------------+
| Next.js default | Yes (if no | Add revalidate | Use dynamic = | "use client" + |
| behavior | dynamic data) | option | "force-dynamic" | useEffect fetch |
+------------------+------------------+------------------+------------------+------------------+
The key insight: Next.js 14+ with App Router defaults to static (SSG). You "opt in" to dynamism. This is the opposite of traditional React apps where everything is CSR by default.
The Decision Flowchart
When an interviewer asks "which rendering strategy would you use?", run through this flowchart in your head:
+---------------------------+
| Does the page need SEO? |
+---------------------------+
/ \
YES NO
/ \
+--------------------+ +------------------+
| Does data change | | Is it highly |
| per user/request? | | interactive? |
+--------------------+ +------------------+
/ \ / \
YES NO YES NO
/ \ / \
+--------+ +-------------+ +------+ +--------+
| SSR | | Does data | | CSR | | SSG |
| | | change at | | | | (why |
| | | all? | | | | not?) |
+--------+ +-------------+ +------+ +--------+
/ \
YES NO
/ \
+-----------+ +--------+
| ISR | | SSG |
| (set | | |
| revalidate| | |
| period) | | |
+-----------+ +--------+
How to read this:
- Start with SEO. If the page doesn't need SEO (admin dashboard, internal tool), CSR or SSG both work.
- If SEO matters, check personalization. If data varies per user (shopping cart, profile page), you need SSR.
- If SEO matters but data is the same for everyone, check freshness. If data never changes (docs, legal pages), use SSG. If data changes periodically (blog, product catalog), use ISR.
Napkin AI Visual Prompt: "Dark gradient (#0a0e1a -> #111827). Diamond-shaped flowchart nodes in emerald green (#10b981) for questions, rectangular answer nodes in purple (#8b5cf6) for SSR/SSG/ISR/CSR. White arrows connecting nodes. White monospace text. Title: 'The Rendering Decision Flowchart'. Clean minimal layout with good spacing."
Interview Answer Templates
The single most valuable tool for rendering strategy questions is a structured answer template. Never just say "I'd use SSR." Say why, say what you considered, and say what you'd combine it with.
Template: "I would choose X because..."
"For [specific page/feature], I would choose [strategy] because:
1. [Data requirement] — the data [changes per user / rarely changes / updates hourly]
2. [SEO requirement] — this page [needs / doesn't need] search engine visibility
3. [Performance priority] — the user expects [instant load / acceptable wait for fresh data]
I'd combine this with [other strategy] for [other parts of the app] because
[reason for mixing]."
Example 1: E-commerce Product Page
"For the product detail page, I would choose ISR with a 60-second revalidate because:
- The product data (price, description, images) changes occasionally but not per-user
- Product pages are critical for SEO — they need to appear in Google results with full content
- Users expect fast load times, and serving from CDN cache gives near-instant response
I'd combine this with CSR for the 'Add to Cart' button and inventory count since those are user-specific and need real-time accuracy. The page shell is ISR, but the interactive parts hydrate on the client."
Example 2: Social Media Dashboard
"For the user dashboard, I would choose SSR because:
- The feed data is personalized per user — every person sees different content
- SEO doesn't matter here since it's behind authentication
- Users expect to see their latest notifications and posts immediately
Actually, since SEO doesn't matter, I'd reconsider — CSR might be simpler here. The initial page shell can be SSR for fast first paint, but the feed itself can load via client-side fetching with loading skeletons. This reduces server load while keeping the experience snappy."
Example 3: Company Blog
"For the blog, I would choose SSG because:
- Blog posts don't change after publication — or change very rarely
- Blog SEO is critical for organic traffic
- Static pages served from CDN give the fastest possible load time
I'd add ISR with on-demand revalidation so that when an author publishes a correction, I can call
revalidatePath('/blog/post-slug')and the page updates without a full rebuild."
Mixing Strategies in One App — The Real-World Pattern
No production Next.js app uses a single rendering strategy. The power of Next.js is that every route can use a different strategy. Here's how a real e-commerce app might break down:
+----------------------------------------------------+
| NEXT.JS E-COMMERCE APP — MIXED STRATEGIES |
+----------------------------------------------------+
| |
| / --> SSG (marketing landing) |
| /about --> SSG (static content) |
| /blog --> SSG (rarely changes) |
| /blog/[slug] --> ISR (revalidate: 3600) |
| /products --> ISR (revalidate: 60) |
| /products/[id] --> ISR (revalidate: 60) |
| /cart --> CSR (user-specific) |
| /checkout --> SSR (secure, per-user) |
| /dashboard --> SSR (personalized) |
| /dashboard/analytics --> CSR (heavy charts) |
| /search --> SSR (dynamic queries) |
| /api/webhook --> Route Handler |
| |
+----------------------------------------------------+
Code: Mixed Strategies in One Next.js App
// app/page.tsx — SSG (default, no dynamic data)
// This page is automatically static at build time
export default function HomePage() {
return (
<main>
<h1>Welcome to Our Store</h1>
<p>The best products, delivered fast.</p>
</main>
);
}
// app/blog/[slug]/page.tsx — ISR with time-based revalidation
// Regenerates in the background every hour
export const revalidate = 3600; // seconds
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return posts.map((post: { slug: string }) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`);
const data = await post.json();
return (
<article>
<h1>{data.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.content }} />
</article>
);
}
// app/products/[id]/page.tsx — ISR with on-demand revalidation
import { revalidateTag } from 'next/cache';
export const revalidate = 60; // Fallback: revalidate every 60 seconds
export default async function ProductPage({ params }: { params: { id: string } }) {
// Tag this fetch so we can revalidate on-demand when inventory changes
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: { tags: [`product-${params.id}`] },
}).then(r => r.json());
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
{/* Client component for interactive "Add to Cart" */}
<AddToCartButton productId={params.id} />
</main>
);
}
// app/dashboard/page.tsx — SSR (forced dynamic, personalized)
import { cookies } from 'next/headers';
// Force this route to render on every request
export const dynamic = 'force-dynamic';
export default async function Dashboard() {
const cookieStore = cookies();
const token = cookieStore.get('session')?.value;
const userData = await fetch('https://api.example.com/me', {
headers: { Authorization: `Bearer ${token}` },
cache: 'no-store', // Never cache personalized data
}).then(r => r.json());
return (
<main>
<h1>Welcome back, {userData.name}</h1>
<p>You have {userData.notifications} new notifications.</p>
{/* Client component for interactive analytics charts */}
<AnalyticsCharts userId={userData.id} />
</main>
);
}
// app/dashboard/analytics/page.tsx — CSR (heavy interactive charts)
'use client';
import { useEffect, useState } from 'react';
export default function AnalyticsPage() {
const [chartData, setChartData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch data on the client — no SEO needed for admin analytics
fetch('/api/analytics')
.then(r => r.json())
.then(data => {
setChartData(data);
setLoading(false);
});
}, []);
if (loading) return <div>Loading analytics...</div>;
return (
<main>
<h1>Analytics Dashboard</h1>
{/* Heavy chart library only loads on client */}
<RevenueChart data={chartData.revenue} />
<TrafficChart data={chartData.traffic} />
</main>
);
}
// app/api/revalidate/route.ts — On-demand revalidation endpoint
// Call this from your CMS webhook when content changes
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const { tag, secret } = await request.json();
// Verify the webhook secret to prevent abuse
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
}
// Revalidate all pages that use this cache tag
revalidateTag(tag);
return NextResponse.json({ revalidated: true, tag });
}
The pattern: Static by default, dynamic where needed, client-side only for heavy interactivity behind auth. This gives you the best of every strategy.
Common Mistakes
1. Using SSR for everything "just to be safe." SSR hits your server on every single request. For pages that don't change (about, docs, legal), SSG is faster, cheaper, and more reliable. SSR should only be used when data is per-request or per-user.
2. Confusing ISR with SSR. ISR still serves static pages — it just rebuilds them in the background. The user gets a cached page instantly. SSR always waits for the server to build a fresh response. ISR is "static with a refresh timer," not "server-rendered slowly."
3. Forgetting that Next.js App Router defaults to static.
If you don't use cookies(), headers(), searchParams, or cache: 'no-store', your route is automatically static. Many developers add dynamic = 'force-dynamic' to every page out of fear — this kills performance. Trust the default and only opt into dynamism when you have a concrete reason.
4. Using CSR when SEO matters. If a page needs to rank in search results, empty HTML with a JavaScript loading spinner is a dealbreaker. Search engines can execute JavaScript, but they deprioritize pages that require it. Server-rendered HTML always wins for SEO.
5. Not mixing strategies within a single page. A page can be ISR at the route level but contain CSR components. The product page is ISR (cached, fast, SEO-friendly), but the "Add to Cart" button is a client component with real-time inventory. Think of strategies at both the route and component level.
Interview Questions
1. An interviewer asks: "You're building a news website. How would you render the homepage vs an article page vs the user profile page?"
The homepage needs SEO and shows the latest articles — I'd use ISR with a short revalidation window (30-60 seconds). This keeps the homepage fast (CDN-cached) while reflecting new articles within a minute. Individual article pages also use ISR (revalidate: 3600) since articles rarely change after publication, but I'd add on-demand revalidation via revalidateTag so editors can push corrections instantly. The user profile page is behind authentication and personalized — I'd use SSR to ensure the user always sees their own data, or CSR if the profile page doesn't need SEO (which it usually doesn't for authenticated users).
2. "Can you have SSG and SSR pages in the same Next.js application? How?"
Yes — this is the core strength of Next.js. Each route segment independently decides its rendering strategy. A marketing page at / can be fully static (SSG), while /dashboard uses export const dynamic = 'force-dynamic' to force SSR. The App Router determines rendering per-route based on whether the route uses dynamic functions like cookies(), headers(), or opts into specific caching behavior. You don't configure this globally — each page.tsx controls its own strategy.
3. "Why not just use SSR everywhere? It's always fresh."
Three reasons: cost, speed, and scalability. SSR runs server-side code on every request, which means higher hosting costs and server load. Static pages served from a CDN are faster because they skip the server entirely — the response comes from an edge node geographically close to the user. At scale (millions of users), SSR requires proportionally more server capacity, while static pages cost almost nothing to serve. Use SSR only when freshness requires it — for everything else, SSG or ISR gives you better performance at lower cost.
4. "What happens when an ISR page's revalidation period expires and a user requests it?"
The user immediately gets the stale (cached) page — they don't wait. In the background, Next.js triggers a regeneration of the page with fresh data. The next user to request the page after regeneration completes gets the new version. This is the stale-while-revalidate pattern: serve stale instantly, update in the background. The user never sees a loading state or a slow response from ISR.
5. "Your e-commerce site has 100,000 products. Would you use SSG with generateStaticParams for all of them?"
Not all at once. I'd generate the most popular products (top 1,000-5,000) at build time using generateStaticParams and let the rest be generated on-demand. When a user visits a product page that wasn't pre-rendered, Next.js renders it on the first request and caches it as static. Subsequent visitors get the cached version. This keeps build times manageable while still serving static pages. I'd set dynamicParams = true (the default) to allow on-demand generation and combine it with ISR (revalidate: 60) so product data stays reasonably fresh.
Quick Reference — The Ultimate Comparison Cheat Sheet
| Question | SSG | ISR | SSR | CSR |
|---|---|---|---|---|
| When is HTML built? | Build time | Build + background refresh | Every request | In the browser |
| Speed for user | Instant (CDN) | Instant (cached) | Depends on server | Fast shell, slow data |
| Data freshness | Build-time only | Within revalidate window | Real-time | Real-time |
| SEO friendly? | Yes | Yes | Yes | No |
| Server cost | Zero | Very low | High | Zero |
| Personalized? | No | No | Yes | Yes |
| Best for | Docs, blogs, marketing | Products, CMS content | Dashboards, search | Admin tools, widgets |
| Next.js config | Default (no dynamic) | revalidate: N | dynamic: 'force-dynamic' | "use client" + useEffect |
| Think of it as | Pre-built house | Pre-built + renovation crew | Custom build per buyer | IKEA flat-pack |
The one-sentence rule: Use the most static strategy your data requirements allow. Start with SSG, add ISR if data changes, escalate to SSR only if data is per-user, and reserve CSR for client-only interactivity.
+----------------------------------------------------------+
| THE RENDERING STRATEGY SPECTRUM |
+----------------------------------------------------------+
| |
| MOST STATIC MOST DYNAMIC |
| (fastest, cheapest) (freshest, costliest) |
| |
| SSG ----> ISR ----> SSR ----> CSR |
| | | | | |
| | | | +-- Interactive widgets |
| | | +------------ Per-user data |
| | +---------------------- Periodic updates |
| +-------------------------------- Never changes |
| |
| Rule: go as far LEFT as your data allows. |
| |
+----------------------------------------------------------+
Prev: Lesson 2.4 -- Client-Side Rendering (CSR) in Next.js Next: Lesson 3.1 -- React Server Components (RSC) ->
This is Lesson 2.5 (Capstone) of the Next.js Interview Prep Course -- 8 chapters, 33 lessons.