Next.js SSR vs SSG vs ISR: A Guide to Choosing Rendering Strategies

“Why does the homepage take 3 seconds to load?” My boss stared at the screen while I stared at the Chrome DevTools waterfall chart—that red TTFB bar stretched out like a snake. That was the first time I realized I might have chosen the wrong rendering strategy from the start.
This isn’t an isolated case. On Next.js GitHub Issues, people ask every day: “Should I use SSR or SSG?” “Why isn’t my ISR configuration working?” To be honest, I was equally confused before. I read the docs, but when it came to actual projects, I still didn’t know which one to choose.
You might have faced similar confusion:
- Used SSR, only to find the initial page load frustratingly slow
- Chose SSG, then had to rebuild the entire site every time content updated
- ISR looked promising, but after configuration, it simply didn’t work
The truth is, there’s no “best” rendering strategy—only the “most suitable” one. Today, let’s figure out which strategy to use in which scenario and how to avoid common pitfalls.
Understanding the Three Rendering Strategies
Before discussing how to choose, let’s clarify these three concepts. I’ll explain them in the most straightforward way possible.
SSG (Static Site Generation): Pre-made Lunch Boxes
Imagine you run a lunch box shop. Every morning, you prepare 100 lunch boxes in advance and place them on the counter. Customers come in and grab one immediately. That’s how SSG works.
Specifically:
- When it’s generated: When you run
next build, Next.js generates HTML for all pages - On user visit: CDN returns the pre-generated HTML file instantly—super fast
- When it fits: Pages with infrequently changing content, like corporate websites, product pages, and blog articles
Clear advantages:
- Lightning fast. According to AWS data, SSG pages load 40-60% faster than SSR
- TTFB (Time to First Byte) is typically under 50ms, as tested by Vercel
- Near-zero server pressure—no problem handling massive traffic
- Low cost, as static file hosting is much cheaper
But with limitations:
- Content updated? Sorry, you need to rebuild the entire site
- With many pages, build time gets long—thousands of pages might take half an hour
- No support for personalized content; all users see the same thing
Put simply, SSG trades “advance preparation” for speed.
SSR (Server-Side Rendering): Cook to Order
Same lunch box shop, but this time you don’t prepare in advance. Whatever customers order, you cook on the spot. That’s SSR.
Specifically:
- When it’s generated: Every user request triggers real-time HTML generation on the server
- On user visit: Wait for the server to query databases, call APIs, render HTML, then return
- When it fits: Pages with real-time changes or requiring personalization
The advantages:
- Content is always fresh—users see the current moment’s data
- Supports personalization based on user’s Cookie, permissions, and different content
- Can access request-time information like headers and query parameters
But at a cost:
- Slow. TTFB typically ranges from 200-500ms, according to Vercel data
- High server pressure—heavy traffic can overwhelm servers
- Expensive, requiring constantly running servers
Real case: An e-commerce site using SSR had initial page load times of 2.3 seconds. After switching to ISR, it dropped directly to 0.8 seconds.
ISR (Incremental Static Regeneration): Hybrid of Pre-made and Cooked-to-Order
This time you’re smart: prepare lunch boxes in advance, but refresh them periodically (say, every hour). Customers usually get pre-made boxes, but the content isn’t too stale. That’s ISR.
Specifically:
- Initial build:
next buildgenerates static HTML (like SSG) - Subsequent updates: Based on your
revalidatesetting, pages are regenerated in the background - On user visit: Usually returns cached HTML (fast), updates in background when expired
A balanced approach:
- Has SSG’s speed (users get static files)
- Has SSR’s freshness (content updates periodically)
- No need to rebuild the entire site every time
Simple configuration (App Router syntax):
// app/posts/[id]/page.tsx
export const revalidate = 60; // Revalidate after 60 seconds
export default async function Post({ params }) {
const post = await getPost(params.id);
return <div>{post.content}</div>;
}But watch out for pitfalls:
- Configuration not working? You might be testing in dev mode (
next dev). ISR only works in production mode (next build+next start) - Platform doesn’t support it? Vercel supports it natively (after all, it’s the Next.js official platform), but other platforms might need extra configuration
Honestly, ISR is my go-to strategy now. It solves the pain points of both SSG and SSR. Really solid.
Decision Tree: Which Strategy for Which Scenario
Okay, concepts cleared. Now the critical question: which one should my project use?
Don’t panic. I’ve organized a decision flow for you. Match it to your project and work through it step by step.
Step 1: Ask Yourself Three Questions
Question 1: Does content need personalization?
If your page needs to display different content based on user identity, permissions, or preferences, go straight to SSR.
Typical scenarios:
- User dashboards (everyone sees different data)
- Personal profile pages
- Shopping cart pages
- Any pages requiring authentication
Why? Personalized content can’t be pre-generated; it must be dynamically generated on user request.
Question 2: How often does content update?
This question determines whether you use SSG or ISR.
Updates every few seconds to minutes: Use SSR
- Examples: stock prices, sports scores, chat messages
- For real-time requirements, SSR is the only choice
Updates every few minutes to hours: Use ISR
- Examples: news sites, blog homepages, product lists, e-commerce inventory
- ISR can set
revalidate: 300(5 minutes)—fast and fresh
Rarely updates, or manually triggered: Use SSG
- Examples: corporate websites, product pages, help docs
- This content might change every few weeks or even months
Question 3: How much traffic do you have?
Traffic volume affects cost and performance considerations.
Very high traffic (millions of PV daily): Prioritize SSG or ISR
- Static files go straight to CDN—low cost, high performance
- With SSR, server pressure gets huge, costs skyrocket
Medium to low traffic: All three work
- If content requires real-time updates, SSR is totally acceptable
- But if ISR works, why not use it?
Step 2: Common Scenarios Cheat Sheet
I’ve organized typical scenarios—find yours:
SSG scenarios:
- ✅ Corporate websites
- ✅ Product pages, pricing pages
- ✅ Marketing landing pages
- ✅ Blog article detail pages (content unchanged after publishing)
- ✅ Technical docs, API docs
- ✅ Help centers, FAQs
Remember the keyword: static, public, infrequently updated
SSR scenarios:
- ✅ Social media feeds (Facebook, Twitter style)
- ✅ User personal dashboards
- ✅ Shopping carts, order pages
- ✅ Real-time data panels
- ✅ Search result pages (generated based on query params)
- ✅ Pages requiring authentication (based on Cookie)
Remember the keyword: dynamic, personalized, real-time
ISR scenarios (my favorite):
- ✅ Blog homepages, article lists (periodically new articles)
- ✅ News sites
- ✅ E-commerce product detail pages (price, inventory update periodically)
- ✅ Forum thread pages (comments, likes can accept a few minutes delay)
- ✅ UGC content platforms (frequent updates but no need for second-level real-time)
- ✅ Weather forecasts, exchange rates
Remember the keyword: periodic updates, acceptable delay, high traffic
Step 3: Hybrid Use is Key
Break this misconception: you don’t have to use just one strategy site-wide.
In reality, most projects use a mix:
- Homepage and product lists use ISR (high traffic, periodic updates)
- Product detail pages use SSG (stable content) or ISR (price, inventory change)
- User dashboards use SSR (personalized)
- About Us, Contact Us use SSG (completely static)
Our team’s e-commerce project does this:
- Homepage (ISR, 10-minute updates)
- Product lists (ISR, 5-minute updates)
- Product details (ISR, 3-minute updates because inventory changes)
- Shopping cart (SSR, must be real-time)
- User center (SSR, personalized)
- About Us, Privacy Policy (SSG, rarely changes)
With this mix, both performance and real-time needs are covered.
One-Sentence Decision Formula
If you’re still unsure, remember this:
Static uses SSG, real-time uses SSR, periodic updates use ISR
When uncertain, try ISR first. It’s the most balanced choice with the lowest error probability.
Three Common Pitfalls and Solutions
After theory, let’s talk practical. These three pitfalls—I’ve hit them all, and you probably will too.
Pitfall 1: ISR’s revalidate Not Working
Symptom: You excitedly configured revalidate: 60, expecting pages to update every minute, but after waiting forever, content stays the same.
My breakdown moment: Really, my first time using ISR, I was stuck here for two whole days. Checked countless docs, modified config N times, just wouldn’t work.
Three reasons:
Dev environment doesn’t support ISR
This is the most common reason. In
next devdev mode, ISR doesn’t work at all! It only works in production mode.Solution:
# Don't test ISR with next dev # Must build first, then start npm run build npm run start # Then test by visiting pagesPlatform doesn’t support it
Not all platforms natively support ISR. Vercel supports it well (after all, it’s official Next.js), but other platforms might need extra configuration.
Like Netlify: Needs the
@netlify/plugin-nextjsplugin to support ISR.How to check: Go to your deployment platform docs and search “Next.js ISR support”.
CDN cache overrides ISR
If you’ve added a CDN or reverse proxy layer (like Cloudflare) in front of Next.js, they might cache the entire response, breaking ISR’s update mechanism.
Solution: Configure CDN to respect
Cache-Controlheaders, or set shorter CDN cache times for ISR pages.
My advice: After configuring ISR, always test in production. Use console.log(new Date()) to print page generation time to verify revalidate is working.
Pitfall 2: SSR Initial Load Painfully Slow
Symptom: After using SSR, initial white screen time reaches 2-3 seconds—users are about to leave.
Real case: We had a project where the homepage used SSR to fetch user info and recommendations. Initial TTFB hit 1.2 seconds, plus rendering time, users waited 3 seconds to see content. Boss shook his head.
Analysis:
SSR slowness usually comes from slow server-side data fetching. Could be:
- Slow API calls (external API high latency)
- Slow database queries (no indexes, complex queries)
- Serial requests (one API waits for another)
- Poor server performance or far geographic location
Four solutions:
Use Streaming SSR and Suspense (React 18+)
Don’t wait for all data before rendering. Return page framework first, stream data when it arrives.
// app/dashboard/page.tsx import { Suspense } from 'react'; export default function Dashboard() { return ( <div> <h1>User Dashboard</h1> {/* Fast rendering part */} <UserInfo /> {/* Slow data wrapped in Suspense */} <Suspense fallback={<div>Loading stats...</div>}> <SlowStats /> </Suspense> </div> ); }Users see pages much faster, much better experience.
Parallel data requests
Make multiple API calls run in parallel:
// ❌ Serial: slow const user = await getUser(); const posts = await getPosts(user.id); // ✅ Parallel: fast const [user, posts] = await Promise.all([ getUser(), getPosts() ]);Consider switching to ISR
Many times you think you need SSR, but ISR is enough.
Ask yourself: Does this page’s data really need to be real-time? If a 1-minute delay is acceptable, use ISR.
Our homepage later switched to ISR (
revalidate: 60), initial load dropped directly from 3 seconds to 0.7 seconds.Deploy to Edge
If you’re using Vercel, try Edge Functions. It deploys your SSR function to global CDN nodes, closer to users, lower latency.
// app/api/data/route.ts export const runtime = 'edge'; // Just this line
Pitfall 3: SSG Build Time Too Long
Symptom: Site has thousands of pages, each next build waits 20-30 minutes, even timing out.
Pain scenario: E-commerce site has 5000 products, blog has 3000 articles. With SSG, build time generates HTML for every page.
Why so slow:
SSG generates HTML for all pages at build time. More pages, longer build time. Plus many pages might never get visited (long-tail products, old articles).
Solutions:
Use fallback strategy
Don’t generate all pages at once. Only generate popular pages, generate others on-demand.
// app/posts/[id]/page.tsx export async function generateStaticParams() { // Only return top 100 articles const posts = await getTopPosts(100); return posts.map(post => ({ id: post.id })); } // Other pages generated on first visit export const dynamicParams = true;Combine with ISR
At build time, only generate homepage and popular pages. Use ISR for other pages on-demand.
This way build time drastically shortens, user visits aren’t slow (because of caching).
Incremental Builds
Vercel supports incremental builds—only rebuild changed pages. If you update one article, no need to rebuild the entire site.
Other platforms might need custom implementation.
Real results:
Our blog project has 2000 articles. Initially pure SSG, build took 15 minutes. Later changed to:
- Build time only generates latest 50 articles
- Other articles use
dynamicParams = trueon-demand generation - All article pages use ISR (
revalidate: 3600)
Now build time dropped to 2 minutes, user visit speed unchanged.
Next.js 15 New Features: React Server Components and PPR
While we’re at it, let me mention two new things Next.js 15 brings. Though they’re not exactly “rendering strategies,” they do affect how we build pages.
React Server Components (RSC): Component-Level SSR
If you’ve used Next.js 13+ App Router, you’re already using Server Components.
How’s it different from traditional SSR?
- Traditional SSR: Entire page renders server-side
- RSC: By default, components render server-side; only interactive components run client-side
Clear benefits:
Smaller JS bundle
Server Component code doesn’t get bundled to the client. Next.js official data shows that with RSC, client-side JavaScript volume can reduce by 30-50%.
Pages load faster, user phones don’t heat up as much.
Direct backend resource access
Server Components can directly query databases, read file systems—no need to build API endpoints.
// app/posts/page.tsx // This is a Server Component, can directly query database async function getPosts() { const posts = await db.posts.findMany(); return posts; } export default async function PostsPage() { const posts = await getPosts(); return ( <div> {posts.map(post => ( <PostCard key={post.id} post={post} /> ))} </div> ); }
Practical advice:
- Default to Server Component (default in App Router)
- Only use Client Component where interaction needed (add
'use client') - Use Server Component for data fetching and static UI; use Client Component for buttons, forms, animations
This way, your app has both SSR’s SEO advantages and a tiny JS bundle.
Partial Prerendering (PPR): Hybrid of Static + Dynamic
PPR is an experimental feature introduced in Next.js 15, letting you mix static and dynamic parts in the same page.
Example:
E-commerce product page:
- Product description, images (static, generated at build time)
- Stock, price (dynamic, fetched on request)
With PPR, static parts are generated at build time, dynamic parts calculated on each request. Fast and fresh.
How to use:
// next.config.js
module.exports = {
experimental: {
ppr: true,
},
};
// app/products/[id]/page.tsx
export const experimental_ppr = true;
export default function ProductPage({ params }) {
return (
<div>
{/* Static part: generated at build time */}
<ProductDescription id={params.id} />
{/* Dynamic part: generated on request */}
<Suspense fallback={<div>Loading...</div>}>
<DynamicStock id={params.id} />
</Suspense>
</div>
);
}But big caveat:
PPR is currently an experimental feature, not recommended for production. Try it on non-critical pages, but don’t go site-wide.
When it stabilizes (maybe Next.js 16?), it’ll be a game-changer.
How Do These New Features Work with SSR/SSG/ISR?
- RSC + SSG: Server Component is static by default, perfect with SSG
- RSC + ISR: Server Component +
revalidate, both static and periodically updated - RSC + SSR: Dynamic routes or setting
dynamic = 'force-dynamic'becomes SSR - PPR: Can be seen as the ultimate fusion of SSG and SSR
My understanding: RSC is Next.js’s underlying architecture upgrade, while SSG/SSR/ISR are rendering strategies based on this architecture. They don’t conflict; instead, they complement each other.
Conclusion
After all this, back to the initial question: how to choose between SSR, SSG, and ISR?
There’s no standard answer. Every project has different needs, traffic, and budget. But as long as you figure out these three core questions, the choice isn’t hard:
- Does content need personalization? → If yes, use SSR
- How often does content update? → Real-time uses SSR, periodic updates use ISR, rarely changes use SSG
- How much traffic? → Very high traffic prioritizes SSG or ISR
Remember the opening scene where my boss asked “why so slow”? Later we changed that homepage from SSR to ISR (revalidate: 60), initial load dropped from 3 seconds to 0.8 seconds. Boss was satisfied, user experience improved.
My advice:
- If uncertain, try ISR first. It’s the most balanced of the three, fast and keeps content fresh
- After project launch, use Chrome DevTools Performance panel to actually test metrics like TTFB, FCP
- Different pages can use different strategies—don’t be dogmatic
Finally, rendering strategy isn’t set in stone. As projects grow, user base increases, needs change, strategies can adjust. Don’t be afraid to change—Next.js’s switching cost is actually quite low.
Hope this article helps you avoid some pitfalls. If you have practical experience or pitfall stories, feel free to share in the comments!
FAQ
When should I use SSR?
• Content is user-specific
• Data changes frequently
• Need real-time data
• SEO is less important than freshness
Example: User dashboard, personalized feeds, real-time chat.
Trade-off: Slower initial load, but always fresh data.
When should I use SSG?
• Content is static or changes rarely
• Need fastest possible load time
• SEO is critical
• Can rebuild on content updates
Example: Blog posts, documentation, marketing pages.
Trade-off: Requires rebuild for updates, but fastest performance.
When should I use ISR?
• Content updates frequently
• Want SSG performance with SSR freshness
• Can accept slightly stale data
• Need balance between performance and freshness
Example: Product pages, news articles, pricing pages.
Trade-off: Best of both worlds, but more complex configuration.
Why isn't my ISR configuration working?
• Testing in dev mode (ISR only works in production)
• revalidate time hasn't passed yet
• Using wrong API (should use fetch with revalidate)
• Build output shows Static instead of ISR
Solutions:
• Test in production build
• Check build output for ISR pages
• Verify revalidate configuration
• Use fetch with next: { revalidate }
How do I choose the right strategy?
1) Does content change frequently? → SSR or ISR
2) Is SEO critical? → SSG or ISR
3) Is performance critical? → SSG or ISR
4) Is data user-specific? → SSR
5) Can you rebuild on updates? → SSG
Most projects use a mix: SSG for static pages, SSR for dynamic pages, ISR for frequently updated content.
Can I mix different strategies?
• Some pages can be SSG
• Some pages can be SSR
• Some pages can be ISR
Example:
• Homepage: SSG (static content)
• Blog posts: ISR (frequently updated)
• User dashboard: SSR (user-specific)
Choose the best strategy for each page.
How do I check which strategy a page uses?
npm run build
Output shows:
• (Static) - SSG
• (SSR) - Server-Side Rendering
• (ISR) - Incremental Static Regeneration
Or check page source:
• SSG: HTML in .next/static
• SSR: No pre-rendered HTML
• ISR: HTML with revalidate headers
13 min read · Published on: Dec 19, 2025 · Modified on: Jan 22, 2026
Related Posts
Next.js E-commerce in Practice: Complete Guide to Shopping Cart and Stripe Payment Implementation

Next.js E-commerce in Practice: Complete Guide to Shopping Cart and Stripe Payment Implementation
Complete Guide to Next.js File Upload: S3/Qiniu Cloud Presigned URL Direct Upload

Complete Guide to Next.js File Upload: S3/Qiniu Cloud Presigned URL Direct Upload
Next.js Unit Testing Guide: Complete Jest + React Testing Library Setup


Comments
Sign in with GitHub to leave a comment