Building with Next.js means choosing how and where your pages get rendered. Each strategy trades off between performance, SEO, data freshness, and server cost — and picking the right one for each page makes a real difference.
Before we dive in, one important thing: Next.js App Router defaults to React Server Components (RSC). Every component renders on the server and ships zero JavaScript to the client unless you explicitly add 'use client'. This is fundamentally different from the old Pages Router — and it shapes how all four rendering strategies work today.
1. Client-Side Rendering (CSR)
In App Router, pure CSR is actually something you opt into. You mark a component with 'use client', and it hydrates on the client after the initial server render.
Client → Server (sends pre-rendered HTML + JS bundle) → Client (hydrates) → API → Client (re-renders with data)
Note that even 'use client' components get server-rendered HTML on first load — the browser doesn't see a blank page. But the data fetching still happens on the client after hydration.
Best for: Interactive widgets, dashboards, authenticated areas — anywhere you need browser APIs or real-time state.
2. Server-Side Rendering (SSR)
In App Router, SSR happens when a Server Component fetches data dynamically. By default, fetch() requests are cached — to opt into per-request rendering, you use cache: 'no-store' or mark the page as dynamic.
// app/news/page.tsx — renders fresh on every requestexport const dynamic = 'force-dynamic'export default async function NewsPage() { const res = await fetch('https://api.example.com/news', { cache: 'no-store', }) const articles = await res.json() return ( <ul> {articles.map(
Data flow:
Client → Server → Database/API → Server (builds HTML) → Client (displays immediately)
The user sees a complete page right away, and search engines get fully rendered content. The trade-off is that every request hits the server.
Best for: Pages where content changes on every request — news feeds, user profiles, search results.
3. Static Site Generation (SSG)
SSG pre-renders pages at build time. In App Router, this is the default behavior — if your page doesn't fetch dynamic data, it's automatically static. For dynamic routes, you use generateStaticParams() to tell Next.js which paths to pre-build.
// app/blog/[slug]/page.tsx — pre-rendered at build timeexport async function generateStaticParams() { const posts = await getAllPostSlugs() return posts.map(slug => ({ slug }))}export default async function BlogPost({ params }: { params: { slug: string } }) { const post = await
Data flow:
Build time: Server → API/Database → Static HTML generated
Request time: Client → CDN (serves pre-built page instantly)
This is as fast as it gets. The content is frozen at build time — if your data changes, users see stale content until the next deploy. This very site uses SSG for blog posts — the content lives in MDX files and only changes when I push a new commit.
Best for: Blog posts, documentation, marketing pages — content that changes infrequently.
4. Incremental Static Regeneration (ISR)
ISR combines the speed of SSG with the ability to update content without a full redeploy. You export a revalidate interval, and Next.js serves the cached page until it expires. When it does, the next request triggers a background regeneration.
1st request: Client → CDN (serves cached HTML instantly)
Background: Server → API/Database → New HTML generated and cached
Next request: Client → CDN (serves the updated page)
The slight catch is that one user always gets the stale version that triggers the rebuild. But for most use cases, a few seconds of staleness is a worthwhile trade for near-instant load times.
Best for: Product pages, listings, blog indexes — content that updates regularly but doesn't need to be real-time.
Bonus: Partial Prerendering (PPR)
Starting from Next.js 15, there's a fifth option that blurs the line between static and dynamic. Partial Prerendering lets a single page be partially static and partially dynamic — the static shell is served instantly from the CDN, while dynamic parts stream in as they become ready.
// app/page.tsx — static shell + dynamic contentimport { Suspense } from 'react'export default function HomePage() { return ( <div> <h1>Welcome</h1> {/* Static — served from CDN */} <Suspense fallback={<p>Loading...</p>}> <RecommendedItems /> {/* Dynamic — streams in */} </Suspense>
PPR is the natural evolution of these four strategies — instead of choosing one mode for the entire page, you get granular control at the component level. It's still relatively new, but it's where Next.js rendering is heading.
Comparison
Method
First Load
SEO
Data Freshness
Next.js Config
CSR
Medium
OK*
Live (client-side)
'use client' + useEffect
SSR
Medium
Good
Live (per-request)
dynamic = 'force-dynamic'
SSG
Fast
Good
Fixed at build time
Default / generateStaticParams()
ISR
Fast
Good
Revalidated
export const revalidate = N
PPR
Fast
Good
Mixed
Suspense boundaries + dynamic data
*CSR components still get server-rendered HTML on first load in App Router, so SEO is better than traditional SPA.
My Rule of Thumb
Default to SSG or ISR — they're fast, cheap, and cover most pages.
Reach for SSR when content genuinely differs per request or per user.
Reserve CSR for interactive widgets or pages behind authentication.
Consider PPR when a page has both static and dynamic sections — don't make the whole page dynamic just because one part needs fresh data.
Static where possible, dynamic where necessary. Let the framework handle the rendering complexity so you can focus on what actually matters — delivering value to users.