← BACK TO DEVLOG

February 20, 2025

·

4 min read

SSR vs SSG: A Practical Decision Guide for 2025

Stop guessing. Here's a framework for deciding when to use Server-Side Rendering, Static Generation, or ISR in Next.js — with real tradeoffs.

Next.jsSSRPerformanceArchitecture

The Eternal Debate

Every new Next.js project starts with the same question: SSR or SSG?

The documentation explains both. The internet has a thousand opinions. But most of them miss the actual decision logic that matters in production.

Here's the framework I use on every project.

The Core Question

Before picking a rendering strategy, ask this:

How stale is "too stale" for this data?

That question maps almost directly to your rendering approach.

The Decision Matrix

| Staleness tolerance | Strategy | Next.js approach | |---|---|---| | Real-time (< 1s) | SSR | export const dynamic = 'force-dynamic' | | Minutes to hours | ISR | export const revalidate = 3600 | | Hours to days | SSG with revalidate | generateStaticParams + revalidate | | Static forever | SSG | generateStaticParams, no revalidate |

When to Use SSR

Use SSR when:

  1. Data is personalized per user — cart contents, account info, localized pricing
  2. Data changes faster than your revalidation window — stock levels, live scores, auction prices
  3. Request context matters — cookies, auth headers, A/B test assignments
// Opt into SSR for this page
export const dynamic = 'force-dynamic';

export default async function CartPage() {
  const cart = await getCart(); // always fresh
  return <CartView items={cart.items} />;
}

The tradeoff: every request hits your server. You pay in latency and compute. Worth it when freshness is non-negotiable.

When to Use SSG

Use SSG when:

  1. Content is shared across all users — blog posts, marketing pages, docs
  2. Data changes infrequently — product descriptions, team bios, pricing tiers
  3. Maximum performance is the goal — pre-rendered HTML served from CDN edge
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(p => ({ slug: p.slug }));
}
// No revalidate = static until next deploy

A static page served from a CDN edge will always beat a server-rendered equivalent. This is the physics of the web.

The ISR Sweet Spot

ISR is the most underused strategy. It gives you:

  • Pre-built static pages (fast first load)
  • Automatic revalidation after a time window
  • On-demand purging with cache tags
export const revalidate = 3600; // rebuild stale pages every hour

export default async function ProductPage({ params }) {
  const product = await getProduct(params.slug);
  return <ProductView product={product} />;
}

For e-commerce, ISR is usually the right answer. Product pages are mostly static — but inventory, price, and availability need to update.

React Server Components Change the Game

With RSC, you can mix strategies at the component level:

// This page is statically generated...
export const revalidate = 3600;

export default async function ProductPage({ params }) {
  const product = await getProduct(params.slug); // static, cached

  return (
    <div>
      <ProductDetails product={product} /> {/* static RSC */}
      <Suspense fallback={<StockSkeleton />}>
        <LiveStockLevel sku={product.sku} /> {/* dynamic RSC */}
      </Suspense>
    </div>
  );
}

The page skeleton is static and fast. The live stock level streams in dynamically. You get the best of both worlds.

The Rule I Follow

Default to SSG. Add ISR when data changes. Add SSR only when you truly need real-time or per-user data.

Most sites should be mostly static. The web was built on static. Static is fast, cheap, and resilient.

Every time you add force-dynamic, you're making a conscious tradeoff. Make that tradeoff deliberately.

Conclusion

The SSR vs SSG debate is usually the wrong question. The right question is:

"What does this specific piece of content need?"

Answer that, apply the matrix, and you'll pick the right strategy almost every time.