Next.js ⚑ Recently updatedUpdated on Jun 30, 2026 β€” this content may still be evolving.

Next.js: Rendering Patterns

Deep dive into SSR, SSG, ISR, and CSR β€” the #1 Next.js interview topic.


Overview

PatternWhen HTML is generatedFreshnessBest for
CSRBrowser (JS)Real-time client dataDashboards, auth-gated apps
SSRServer per requestAlways freshPersonalized, frequently changing
SSGBuild timeStatic until rebuildBlogs, marketing, docs
ISRBuild + periodic regenStale-while-revalidateLarge catalogs, news

CSR β€” Client-Side Rendering

Browser downloads minimal HTML + JS bundle. React renders in browser. Data fetched client-side.

How it works

  1. Server sends shell HTML + JS
  2. JS loads, React mounts (hydration if SSR shell exists)
  3. useEffect or React Query fetches data
  4. UI updates

App Router Example

'use client';

import { useEffect, useState } from 'react';

export default function Dashboard() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/metrics')
      .then(r => r.json())
      .then(setData);
  }, []);

  if (!data) return <p>Loading...</p>;
  return <Chart data={data} />;
}

Pros

  • Simple mental model (classic React)
  • Great for highly interactive, user-specific UI
  • No server load for data fetching

Cons

  • Slower First Contentful Paint
  • Poor SEO for content-heavy pages (empty until JS runs)
  • Loading spinners on first visit

When to use

  • Admin dashboards
  • Pages behind authentication
  • Heavy client interactivity (charts, drag-drop editors)

SSR β€” Server-Side Rendering

HTML generated on every request on the server. Sent complete to browser. React hydrates for interactivity.

How it works

  1. User requests page
  2. Server runs React, fetches data, renders HTML
  3. Full HTML sent to browser (fast FCP, SEO-friendly)
  4. JS loads β†’ hydration attaches event listeners

App Router Example (Server Component)

// app/users/[id]/page.tsx β€” Server Component by default
async function getUser(id: string) {
  const res = await fetch(`https://api.example.com/users/${id}`, {
    cache: 'no-store' // SSR β€” no caching
  });
  if (!res.ok) throw new Error('Failed');
  return res.json();
}

export default async function UserPage({ params }: { params: { id: string } }) {
  const user = await getUser(params.id);
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Pages Router Example

// pages/users/[id].js
export async function getServerSideProps({ params }) {
  const res = await fetch(`https://api.example.com/users/${params.id}`);
  const user = await res.json();
  return { props: { user } };
}

export default function UserPage({ user }) {
  return <h1>{user.name}</h1>;
}

Pros

  • Fresh data every request
  • Great SEO
  • Fast initial paint with real content

Cons

  • Server load scales with traffic
  • Slower TTFB than static CDN
  • Can’t cache page at CDN edge (by default)

When to use

  • Personalized pages (user profile, feed)
  • Data changes frequently
  • SEO matters AND content is dynamic

SSG β€” Static Site Generation

HTML generated at build time. Same HTML served to all users from CDN.

How it works

  1. next build fetches data and renders pages to HTML
  2. Static files deployed to CDN
  3. Every user gets identical pre-built HTML
  4. Hydration on client

App Router Example

// fetch cached by default in Server Components
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    cache: 'force-cache' // default β€” static at build
  });
  return res.json();
}

export default async function BlogPage() {
  const posts = await getPosts();
  return (
    <ul>
      {posts.map(p => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}

Pages Router Example

export async function getStaticProps() {
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();
  return { props: { posts } };
}

export async function getStaticPaths() {
  const posts = await getAllPosts();
  return {
    paths: posts.map(p => ({ params: { slug: p.slug } })),
    fallback: false // or 'blocking' / true
  };
}

Pros

  • Fastest possible delivery (CDN)
  • Lowest server cost
  • Excellent SEO

Cons

  • Data stale until rebuild
  • Build time grows with page count
  • Not for personalized per-user content

When to use

  • Marketing pages, landing pages
  • Blog posts, documentation
  • Product catalogs that change infrequently

ISR β€” Incremental Static Regeneration

SSG + automatic background revalidation. Serve stale page instantly, regenerate in background.

How it works

  1. Page built at build time (or first request)
  2. Cached at CDN with revalidation interval
  3. After interval, next request triggers background rebuild
  4. Subsequent requests get fresh page

App Router Example

async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 } // regenerate at most every 60 seconds
  });
  return res.json();
}

Pages Router Example

export async function getStaticProps() {
  const products = await fetchProducts();
  return {
    props: { products },
    revalidate: 60 // seconds
  };
}

fallback in dynamic SSG

// getStaticPaths with fallback: 'blocking'
// First visit to unknown path β†’ server renders, caches, then serves

Pros

  • CDN speed + reasonably fresh data
  • No full rebuild for content updates
  • Scales to millions of pages

Cons

  • Users may see slightly stale content (stale-while-revalidate)
  • Harder to reason about than pure SSG
  • First visitor after expiry waits for regen (with blocking)

When to use

  • E-commerce product pages
  • News sites
  • Large blogs (10k+ pages)

Decision Flowchart

Does page need to be personalized per user?
β”œβ”€β”€ YES β†’ SSR or CSR (behind auth)
└── NO β†’ Does content change frequently?
    β”œβ”€β”€ YES (every few minutes) β†’ ISR
    β”œβ”€β”€ YES (every request) β†’ SSR
    └── NO β†’ SSG

Hydration

Server sends HTML. Client JS β€œhydrates” β€” React attaches to existing DOM without re-creating it.

Mismatch error: Server HTML β‰  client render. Causes:

  • Date.now() or Math.random() in render
  • Browser-only APIs in Server Components
  • Invalid HTML nesting

Fix: Use useEffect for client-only values, or 'use client' boundary.


Server vs Client Components (App Router)

Server ComponentClient Component
Default in App Router'use client' directive
Can async/await directlyUses hooks, event handlers
No useState/useEffectFull interactivity
Smaller JS bundleShips JS to browser

Pattern: Server Component fetches data β†’ passes to Client Component as props.

// app/page.tsx (Server)
import { ProductList } from './ProductList';

async function getProducts() {
  const res = await fetch('...', { next: { revalidate: 3600 } });
  return res.json();
}

export default async function Page() {
  const products = await getProducts();
  return <ProductList initialProducts={products} />;
}
// ProductList.tsx
'use client';

export function ProductList({ initialProducts }) {
  const [filter, setFilter] = useState('');
  // interactive filtering on client
}

fetch Caching Cheat Sheet (App Router)

OptionBehavior
DefaultCache (SSG-like at build)
{ cache: 'no-store' }SSR β€” fresh every request
{ next: { revalidate: 60 } }ISR β€” 60 second window
{ next: { tags: ['posts'] } }On-demand revalidation

Interview Comparison Table

CSRSSRSSGISR
SEOPoorExcellentExcellentExcellent
TTFBFast shellSlowerFastestFast
Data freshnessClient fetchPer requestBuild timeConfigurable
Server costLowHighLowestLow
CDN cacheableLimitedNoYesYes

What to Say in Interviews

β€œFor my blog I used SSG because content rarely changes and SEO matters. For the user dashboard I used CSR with client fetch because it’s behind auth and highly interactive. If I had a product catalog updating hourly, I’d use ISR with a 60-second revalidate.”

That sentence alone shows more maturity than listing definitions.