Skip to main content
CodeOath
← All posts

Next.js75 min total · 12 parts

Next.js Fundamentals: The App Router, Server Components, and the Caching Rules That Just Flipped

Part 8 of 12 · ~3 min

Rendering Strategies

Set SSR, SSG, and ISR side by side and they stop looking like three different features — they're three answers to one timing question: at what point does this page's HTML actually get built?

StrategyWhen the HTML gets builtFits well for
SSR (server-side rendering)Fresh, on every requestData that's specific to one visitor, or has to be current right now — an employer's own dashboard
SSG (static site generation)Once, ahead of timeContent that's the same for every visitor and barely ever changes — an About page
ISR (incremental static regeneration)Ahead of time, then refreshed on a timer or an eventMostly-static content that does shift occasionally — job listings, a single job's page

The Pages Router made you pick a lane with a dedicated function per strategy — write getServerSideProps and you'd built SSR, write getStaticProps and you'd built SSG. The App Router has no such menu; instead, whichever of the three a page ends up as falls straight out of the fetch-caching choices from a couple of chapters back. And that's exactly where the default flip from chapter 5 changes the outcome: a page that fetches with no options at all now behaves like the SSR row above, not the SSG row, which is backwards from how this used to shake out:

// Effectively SSR — an applicant list belongs to exactly one
// currently-signed-in employer and must never reach anyone else
async function getApplicants(employerId: string) {
  const res = await fetch(`https://api.openroles.example/employers/${employerId}/applicants`, {
    cache: "no-store",
  });
  return res.json();
}

// Effectively SSG — About-page copy that only moves when
// someone on the team edits it and redeploys
async function getAboutCopy() {
  const res = await fetch("https://api.openroles.example/pages/about", {
    cache: "force-cache",
  });
  return res.json();
}

// Effectively ISR — job listings, tagged and cleared on publish
// (from chapter 5), plus a generous time window as a backstop
async function getJobs() {
  const res = await fetch("https://api.openroles.example/jobs", {
    next: { tags: ["jobs"], revalidate: 3600 },
  });
  return res.json();
}

Two questions, asked honestly about whatever page you're looking at, are usually enough to pick correctly: does every visitor see the same thing, and if the data behind it drifts by a few minutes, does that actually cost anyone anything?

  • An employer's applicant list is specific to one employer and has to be right now — that's SSR, full stop, no-store, no negotiating. Handing anyone a cached copy of a different employer's applicants isn't a performance shortcut, it's a breach.
  • The About page looks identical to every visitor and only changes when someone on the team ships new copy. That's SSG — regenerating the same HTML on every single request buys nothing.
  • The listings, and each job's own page, sit in the interesting middle: identical for every reader, but genuinely capable of changing — a new posting, an edited salary band — without needing that change reflected instantly. ISR, whether through a time window or revalidateTag/revalidatePath fired the moment the underlying record actually changes, gets you the speed of a static page without staying wrong for hours at a time.

Worth flagging here, since it's not mentioned anywhere in the live version of this reference: Next.js 16 also introduced a more explicit option in this same territory — Partial Prerendering, folded into the opt-in Cache Components model from the last chapter. Rather than picking one strategy for an entire page, it lets a single route ship a static shell instantly — a job's title, description, salary band — while genuinely dynamic pieces inside that same shell, like "applied 12 minutes ago" computed at request time, stream in behind a <Suspense> boundary. It's a real, current capability, worth being aware of; it's also new and optional enough that most Next.js 16 apps running today, OpenRoles included, are still built on the classic three-strategy model above.