SSR/SSG Advisor
Choose the optimal rendering strategy for Next.js pages based on requirements.
Quick Start
Decision criteria:
-
SSG: Static content, pre-render at build → Best performance
-
ISR: Static with updates, revalidate periodically → Balance of both
-
SSR: Dynamic per-request, personalized → Fresh data
-
CSR: Client-side only, highly interactive → User-specific
Instructions
Step 1: Analyze Content Requirements
Ask these questions:
-
Does content change per user? (personalization)
-
How frequently does content update?
-
Is SEO critical?
-
What's the acceptable data freshness?
-
How many pages need to be generated?
Step 2: Choose Rendering Strategy
Static Site Generation (SSG):
// pages/products/[id].tsx export async function getStaticProps({ params }) { const product = await fetchProduct(params.id);
return { props: { product }, // Optional: revalidate for ISR // revalidate: 60, // seconds }; }
export async function getStaticPaths() { const products = await fetchAllProducts();
return { paths: products.map(p => ({ params: { id: p.id } })), fallback: 'blocking', // or false, or true }; }
When to use SSG:
-
Marketing pages
-
Blog posts
-
Documentation
-
Product catalogs (if manageable size)
-
Any content that doesn't change often
Server-Side Rendering (SSR):
// pages/dashboard.tsx export async function getServerSideProps(context) { const session = await getSession(context); const data = await fetchUserData(session.userId);
return { props: { data }, }; }
When to use SSR:
-
User dashboards
-
Personalized content
-
Real-time data
-
Content requiring authentication
-
Frequently changing data
Incremental Static Regeneration (ISR):
// pages/blog/[slug].tsx export async function getStaticProps({ params }) { const post = await fetchPost(params.slug);
return { props: { post }, revalidate: 60, // Regenerate every 60 seconds }; }
When to use ISR:
-
Blog with frequent updates
-
Product pages with price changes
-
News sites
-
Content that updates periodically
-
Large sites where full rebuild is slow
Client-Side Rendering (CSR):
'use client'; // App Router
import { useEffect, useState } from 'react';
function Dashboard() { const [data, setData] = useState(null);
useEffect(() => { fetch('/api/user-data') .then(res => res.json()) .then(setData); }, []);
return <div>{data ? <Content data={data} /> : <Loading />}</div>; }
When to use CSR:
-
Highly interactive UIs
-
User-specific data (after auth)
-
Real-time updates
-
SEO not required
-
Data behind authentication
Step 3: Implement Data Fetching
App Router (Next.js 13+):
// app/products/page.tsx async function ProductsPage() { // SSG: cached by default const products = await fetch('https://api.example.com/products');
// ISR: revalidate periodically const products = await fetch('https://api.example.com/products', { next: { revalidate: 3600 } // 1 hour });
// SSR: no caching const products = await fetch('https://api.example.com/products', { cache: 'no-store' });
return <ProductList products={products} />; }
Pages Router:
// getStaticProps: SSG/ISR // getServerSideProps: SSR // useEffect + fetch: CSR
Step 4: Configure Caching and Revalidation
On-demand revalidation:
// app/api/revalidate/route.ts import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) { const path = request.nextUrl.searchParams.get('path');
if (path) { revalidatePath(path); return Response.json({ revalidated: true }); }
return Response.json({ revalidated: false }); }
Tagged caching:
// Fetch with tags const data = await fetch('https://api.example.com/products', { next: { tags: ['products'] } });
// Revalidate by tag revalidateTag('products');
Step 5: Handle Fallback Strategies
getStaticPaths fallback options:
export async function getStaticPaths() { return { paths: [...], fallback: false, // 404 for non-pre-rendered paths // fallback: true, // Generate on-demand, show loading // fallback: 'blocking', // Generate on-demand, wait for page }; }
Common Patterns
Hybrid Approach
// Mix strategies in same app // - SSG for marketing pages // - ISR for blog posts // - SSR for user dashboard // - CSR for interactive features
// app/layout.tsx (SSG) export default function RootLayout({ children }) { return <html><body>{children}</body></html>; }
// app/blog/[slug]/page.tsx (ISR)
async function BlogPost({ params }) {
const post = await fetch(/api/posts/${params.slug}, {
next: { revalidate: 60 }
});
return <Article post={post} />;
}
// app/dashboard/page.tsx (SSR) async function Dashboard() { const data = await fetch('/api/user', { cache: 'no-store' }); return <DashboardContent data={data} />; }
Optimistic UI with ISR
// Show stale data immediately, revalidate in background export async function getStaticProps() { const data = await fetchData();
return { props: { data }, revalidate: 1, // Revalidate every second }; }
Conditional Rendering
// Different rendering based on route export async function getServerSideProps(context) { const { preview } = context;
if (preview) { // SSR for preview mode const data = await fetchDraftContent(); return { props: { data, preview: true } }; }
// Redirect to SSG version return { redirect: { destination: '/static-version', permanent: false, }, }; }
Decision Matrix
Requirement SSG ISR SSR CSR
SEO Critical ✅ ✅ ✅ ❌
Fast TTFB ✅ ✅ ❌ ❌
Fresh Data ❌ ⚠️ ✅ ✅
Personalized ❌ ❌ ✅ ✅
Large Scale ⚠️ ✅ ✅ ✅
Build Time ❌ ✅ ✅ ✅
✅ = Excellent, ⚠️ = Acceptable, ❌ = Poor
Performance Considerations
SSG:
-
Fastest: Pre-rendered at build time
-
Best for CDN caching
-
Long build times for large sites
-
Stale data until next build
ISR:
-
Fast initial load (cached)
-
Automatic updates
-
Best of both worlds
-
Slight delay for revalidation
SSR:
-
Always fresh data
-
Slower TTFB
-
Higher server load
-
Can't be cached at CDN edge
CSR:
-
Slow initial load
-
No SEO benefits
-
Reduces server load
-
Best for authenticated content
Troubleshooting
Build taking too long:
-
Use ISR instead of SSG
-
Reduce number of pre-rendered paths
-
Use fallback: 'blocking'
Stale data showing:
-
Reduce revalidate time
-
Implement on-demand revalidation
-
Consider SSR for critical data
High server costs:
-
Move from SSR to ISR where possible
-
Implement proper caching
-
Use CDN for static assets
SEO issues:
-
Avoid CSR for public content
-
Use SSG or SSR
-
Implement proper metadata
Best Practices
-
Start with SSG: Default to static, move to dynamic only when needed
-
Use ISR for updates: Better than full SSR for most cases
-
Combine strategies: Different pages can use different methods
-
Cache aggressively: Use CDN and browser caching
-
Monitor performance: Track TTFB, build times, server load
-
Implement fallbacks: Handle errors and loading states
-
Test thoroughly: Verify behavior in production