Server-Side Rendering vs Static Site Generation in Next.js: A Performance-First Decision Matrix
The Question Nobody Asks But Should
You're building a Next.js app. Your PM asks: "Will this be fast?" Half the room debates SSR vs SSG. Nobody mentions ISR.
This isn't a binary choice. It's a spectrum. Picking wrong costs you real money, users, and time debugging performance issues.
Let me show you how I actually think through this decision.
The Architecture Truth Nobody Tells You
SSG and SSR aren't just rendering modes—they're architectural commitments with cascading effects.
SSG commits to:
- Build time scaling with content volume
- Cache invalidation complexity
- Deploy frequency tied to content changes
SSR commits to:
- Runtime CPU overhead
- Edge deployment requirements
- Stateful infrastructure
ISR (the unicorn):
- Revalidates on a schedule
- Regenerates on-demand
- Feels like SSG at scale
Decision Framework: The Real Factors
Factor 1: Content Velocity
This is what most frameworks miss. It's about how often content changes.
- High velocity: Blog comments, live stats, user-generated content, stock prices
- Medium velocity: Product catalogs, blog posts, news articles, user profiles
- Low velocity: Legal pages, pricing pages, documentation, whitepapers
If content changes faster than your revalidation window, SSR wins.
Factor 2: Personalization Coefficient
SSG breaks when personalization is involved due to long build times.
The Rule: If you need context.query or context.headers, go SSR.
Factor 3: Data Freshness Requirements
Different pages need different freshness levels:
- SSG with ISR: Blogs, product pages, etc.
- SSR: Real-time dashboards
- ISR: Product pages with 10-minute updates
Performance Reality Check
SSG Performance Profile
- Build time: 45s
- TTFB: 20ms
- FCP: 80ms
- Lighthouse Score: 98/100
- Infrastructure: Static hosting ($5-20/month)
- Failure mode: Typo in production takes 2:45 to fix.
SSR Performance Profile
- Build time: 5s
- TTFB: 200-800ms
- FCP: 300-1200ms
- Lighthouse Score: 75-90/100
- Infrastructure: $200-1000+/month
- Failure mode: Slow DB query leads to spinners.
Mixed Approach (Default)
- Homepage: SSG + ISR (revalidate: 3600)
- Blog posts: SSG + ISR (revalidate: 86400)
- User dashboard: SSR
- Product listings: SSG + ISR (revalidate: 600)
- Admin panel: SSR only
Result: 95% of traffic hits CDN cache, average TTFB: 40ms.
Code Decision Matrix
// pages/architecture-decision.jsx export default function ArchitectureDecision({ contentType, changeFrequency, userCount, personalizedContent }) { const renderingStrategy = () => { if (!personalizedContent && changeFrequency === 'monthly') { return { mode: 'SSG', revalidate: 86400 * 30, cost: 'Minimal', reasoning: 'Static hosting' }; } if (!personalizedContent && changeFrequency === 'daily') { return { mode: 'ISR', revalidate: 3600, cost: 'Low', reasoning: 'Fresh, background regeneration' }; } if (personalizedContent || changeFrequency === 'hourly') { return { mode: 'SSR', revalidate: 0, cost: 'High', reasoning: 'Needs computation on every request' }; } if (personalizedContent && userCount > 100000 && changeFrequency === 'realtime') { return { mode: 'Edge Rendering', revalidate: 0, cost: 'Very High', reasoning: 'Compute at edge' }; } return { mode: 'Hybrid', revalidate: 'Variable' }; }; return renderingStrategy(); }
Real-World Implementation
Scenario 1: E-commerce Product Page
export default function ProductPage({ product, stock, reviews }) { return ( <> <h1>{product.name}</h1> <p>{product.description}</p> <StockIndicator count={stock} /> {/* Real-time */} <ReviewSection reviews={reviews} /> {/* Real-time */} <AddToCartButton /> {/* Real-time */} </> ); } export async function getStaticProps({ params }) { const product = await db.products.findBySlug(params.slug); return { props: { product }, revalidate: 1800 }; // Regenerate every 30 mins }
Scenario 2: News Website
export default function ArticlePage({ article }) { return ( <article> <h1>{article.title}</h1> <p>{article.content}</p> <time>{article.publishedAt}</time> </article> ); } export async function getStaticProps({ params }) { const article = await db.articles.findBySlug(params.slug); if (!article) return { notFound: true }; return { props: { article }, revalidate: 60 }; // Regenerate every minute }
Scenario 3: User Dashboard (SSR)
export default function Dashboard({ user, stats }) { return ( <div> <h1>Welcome, {user.name}</h1> <StatsCard stats={stats} /> </div> ); } export async function getServerSideProps(context) { const session = await getSession(context); if (!session) return { redirect: { destination: '/login' } }; const user = await db.users.findById(session.userId); const stats = await db.stats.getUserStats(session.userId); return { props: { user, stats } }; }
The Hidden Gotchas
Gotcha 1: Build Time Explosion
export async function getStaticPaths() { const users = await db.users.findAll(); // 1 million users = 6-hour build return { paths: users.map((u) => ({ params: { id: u.id } })), fallback: 'blocking' }; }
Better: Generate popular ones, fallback for others.
Gotcha 2: Cache Invalidation Hell
export async function getStaticProps() { const post = await db.posts.findById(params.id); return { props: { post }, revalidate: 86400 }; // Too long } // Solution: Shorter revalidation export async function getStaticProps() { const post = await db.posts.findById(params.id); return { props: { post }, revalidate: 300 }; // 5 minutes }
Gotcha 3: Hybrid Data Lag
export default function Product({ staticPrice, realTimeStock }) { return <h1>Price: ${staticPrice} | Stock: {realTimeStock}</h1>; } // Fix: Fetch both client-side or both server-side
The Decision Tree (Copy This)
Is the page personalized per user?
├─ YES → SSR only
└─ NO → Continue
Does content change hourly or faster?
├─ YES → SSR or Edge Computing
└─ NO → Continue
Do you have thousands of dynamic pages?
├─ YES → ISR with fallback: 'blocking'
└─ NO → Continue
Is this a public page everyone sees the same?
├─ YES → SSG with ISR revalidation
└─ NO → SSR
Final Thoughts
SSR vs SSG isn't a religious debate. It's about infrastructure trade-offs.
Pick SSG when you can predict all pages at build time and content changes rarely.
Pick SSR when you need real-time data or personalization.
Use ISR when you're in between—most of the time.
Make informed decisions based on requirements, not last project experience.