next.js

Server-Side Rendering vs Static Generation in Next.js: When to Use Each

8 min
next.jsperformanceweb development

Server-Side Rendering vs Static Generation in Next.js: When to Use Each

March 23, 20268 min read
Server-Side Rendering vs Static Generation in Next.js: When to Use Each

SSR or SSG? The wrong choice costs you speed or accuracy. Here's a practical framework for making this critical Next.js architectural decision correctly.

The Decision That Shapes Everything

Most developers starting a new Next.js project do the same thing: they render everything server-side by default, and then wonder why their Time to First Byte is higher than expected. Or they reach for static generation everywhere and end up serving stale booking availability to guests.

The choice between server-side rendering (SSR) and static generation (SSG) is arguably the most consequential architectural decision in a Next.js project — and it's one that's frequently made by instinct rather than reasoning. This guide gives you a clear framework for making that decision deliberately, with examples from the hospitality and professional services projects we build at LogicLeap.

What Each Strategy Actually Does

Before the framework, a quick clarification — because even experienced developers sometimes blur the definitions.

Static Generation (SSG)

The page is rendered at build time. When a user requests it, they receive pre-built HTML that's often served from a CDN edge node, not from your server at all. This is the default behaviour for most pages in the Next.js App Router.

typescript
// This page will be statically generated — no special configuration needed
export default async function AboutPage() {
  const res = await fetch('https://cms.example.com/about', {
    next: { revalidate: false } // cache indefinitely
  })
  return <PageContent data={await res.json()} />
}

Speed profile: Exceptional. CDN-served HTML loads in tens of milliseconds globally, with no database query, no server compute, no cold start.

Server-Side Rendering (SSR)

The page is rendered fresh on every request, at request time. The HTML is generated in response to each individual visit.

typescript
// Force dynamic rendering — fresh on every request
export const dynamic = 'force-dynamic'

export default async function BookingPage() {
  const res = await fetch('/api/availability', {
    cache: 'no-store'
  })
  return <BookingWidget data={await res.json()} />
}

Speed profile: Slower than static, but capable of serving always-current data. The latency depends on your server's location, your database response time, and whether you're running a cold serverless function.

Incremental Static Regeneration (ISR)

A hybrid: the page is statically generated, but can be regenerated at intervals or on-demand. The first request after a revalidation triggers a background rebuild; subsequent requests still receive the cached version until it's ready.

typescript
export default async function MenuPage() {
  const res = await fetch('/api/menu', {
    next: { revalidate: 3600 } // regenerate after 1 hour
  })
  return <Menu data={await res.json()} />
}

This is the most underused strategy in the codebases we review. Most content doesn't need to be perfectly fresh at the millisecond level — it needs to be fresh within a reasonable window. ISR delivers that while keeping the speed benefits of static delivery.

When to Use Static Generation

Static generation is the right choice whenever the page content doesn't depend on who's requesting it, and when the data doesn't need to be real-time accurate.

For the hospitality and professional services businesses we build for, this covers the vast majority of pages:

Use static generation for:

  • Marketing pages — homepage, about, services, team. These change infrequently. Render them at build time, cache indefinitely, and serve from the CDN. A statically generated homepage loads in under 80ms from anywhere in the world.
  • Blog posts and editorial content — once published, these don't change. SSG is perfect, with on-demand revalidation triggered when an author updates a post in the CMS.
  • Restaurant menus and product catalogues — assuming menu changes are handled by admin actions rather than real-time inventory depletion, ISR with tag-based revalidation works brilliantly. Update the menu in the CMS, trigger revalidateTag('menu'), and regeneration happens in the background within seconds.
  • Hotel room descriptions, gallery pages, pricing pages — all static. We helped a four-star hotel migrate to Next.js last year; their room detail pages went from 1.8s TTFB (WordPress, server-rendered) to 62ms (Next.js, statically generated and CDN-cached). Guest bounce rate on those pages dropped by 23%.
  • FAQ pages, legal pages, contact pages — always static, with essentially no maintenance burden.

The Hidden Cost of Unnecessary SSR

Every page you render server-side is a page that hits your server on every visit. For a restaurant with 10,000 monthly visitors to their menu page, that's 10,000 server-side renders — each one a database query, a function invocation, a cold start risk — instead of 10,000 CDN cache hits at effectively zero cost.

Static generation isn't just faster. It's also more reliable (no server to go down), cheaper (fewer compute invocations), and more scalable (the CDN handles any traffic spike automatically).

When to Use Server-Side Rendering

SSR is necessary when the response must genuinely vary based on the individual request — not just the data, but who is asking, what session they have, or what moment it is.

Use SSR for:

  • Booking availability and inventory — this is the clearest case. If your hotel has two rooms remaining for next Saturday, showing the wrong count to two different guests simultaneously has real consequences. The availability fetch must bypass all caches and pull current data. cache: 'no-store' is non-negotiable.
  • Authentication-gated content — dashboards, account pages, reservation management, anything that reads from cookies() or headers(). Next.js automatically treats these as dynamic (correctly), because the response depends on the user's session.
  • Personalised content — if the page is customised based on the user's booking history, preferences, or geography, it can't be statically pre-rendered. Consider whether personalisation can be deferred to the client side — a pattern sometimes called "static shell + client fetch" — to preserve the speed of the initial static load.
  • Real-time pricing — yield management systems, dynamic room rates, last-minute pricing engines. Any price that changes based on demand, time of day, or occupancy should be treated as dynamic.
  • Search results — queries containing URL parameters (?q=deluxe+double&date=2026-04-10) can't be pre-rendered. These are inherently request-specific.

The Most Common Mistake: SSR for Static Content

The most frequent architectural error we see in inherited Next.js codebases is SSR being used for pages that don't need it. The tell-tale sign:

typescript
// This is almost always unnecessary for a menu or gallery page
export const dynamic = 'force-dynamic'

We've reviewed projects where every page in the application had dynamic = 'force-dynamic' at the top — added defensively by a developer who wasn't sure of the default behaviour. The result was a Next.js application with worse performance than a straightforward PHP site, paying for all the framework complexity with none of the speed benefits.

A Practical Decision Framework

Here's the framework we apply to every page in a new project:

Step 1: Does the page need request-time data?

Ask: "Can the HTML for this page be identical for every visitor, right now?"

If yes → static generation. If no → continue to step 2.

Step 2: Is the variable data truly real-time critical?

Ask: "Can this data be up to 60 minutes old without causing a real problem?"

If yes (a weekly specials section, an updated team bio) → ISR with a sensible revalidate window. If no (booking availability, payment processing) → SSR with cache: 'no-store'.

Step 3: Can the Dynamic Portion Be Isolated?

Often, a mostly-static page has one small dynamic section. A restaurant homepage is entirely static except for a live availability widget in the booking section. The solution isn't to make the whole page dynamic — it's to keep the page static and load the dynamic portion client-side after the initial render.

typescript
// The page is static — loads instantly from CDN
// The availability widget fetches fresh data after render
export default function RestaurantHomePage() {
  return (
    <main>
      <HeroSection />        {/* Static */}
      <MenuHighlights />     {/* Static */}
      <BookingWidget />      {/* Client component — fetches fresh data */}
      <ReviewsSection />     {/* Static, revalidated daily */}
    </main>
  )
}

This pattern gives you the best of both worlds: a sub-100ms initial page load from the CDN, with live data where it genuinely matters.

Partial Pre-rendering: The Future Direction

Worth mentioning for developers planning ahead: Next.js is actively developing Partial Pre-rendering (PPR), which formalises the "static shell + dynamic holes" pattern at the framework level. Rather than making routing decisions at the page level, PPR allows individual components within a page to be static or dynamic — the static shell is served immediately from the CDN, and dynamic segments stream in as they resolve.

typescript
import { Suspense } from 'react'

export default function MenuPage() {
  return (
    <main>
      <StaticMenuContent />
      <Suspense fallback={<AvailabilitySkeleton />}>
        <LiveAvailability />  {/* Streams in after static shell */}
      </Suspense>
    </main>
  )
}

PPR is still experimental, but it represents where Next.js is heading — a world where the SSR vs SSG binary matters less, and granular component-level rendering decisions become the norm.

Key Takeaways

  • Default to static generation. If you can't articulate why a page needs to be dynamic, it should be static. The performance and cost benefits are significant.
  • ISR covers most "occasionally updated" content. Menus, catalogues, editorial pages, and most business content doesn't need to be always-fresh — it needs to be fresh within a predictable window.
  • Reserve SSR for data where staleness causes real harm. Booking availability, payment flows, personalised content, and authentication-gated pages are the legitimate use cases.
  • Isolate dynamic components rather than making whole pages dynamic. A static page with a dynamic widget is almost always preferable to a fully dynamic page.
  • **Audit inherited codebases for unnecessary force-dynamic usage.** It's one of the easiest performance wins available — and we find it in the majority of projects we inherit.
  • PPR is on the horizon. Start thinking in terms of static shells and dynamic suspense boundaries now; that's the direction the framework is heading.

Building Performance In From the Start

The rendering strategy you choose for each page shapes every performance metric your site will ever achieve. A wrong default — SSR for content that should be static — is a ceiling you can't easily raise later without architectural changes.

We build Next.js sites for hospitality businesses, professional services companies, and SaaS platforms where both performance and accuracy matter. The architectural decisions we make early — which pages render statically, which fetch fresh data, how cache invalidation propagates through the system — are the foundation everything else is built on.

If you're planning a new Next.js project, or you've inherited one and suspect the rendering strategy wasn't thought through carefully, get in touch. We'll tell you exactly where the problems are and how to fix them.

Need help implementing this?

We build high-performance websites and automate workflows for ambitious brands. Let's talk about how we can help your business grow.