Back to Notelogs
•
#NextJs#SSR#ISR#SSG
Server-Side Rendering vs Static Site Generation in Next.js: A Performance-First Decision Matrix

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.