Next Rendering Strategies¶
SSR , SSG , ISR , CSR - four ways to render the same React component with dramatically different performance , freshness , and cost profiles
Next.js lets you pick per page or even per component. One misconfigured revalidation interval and you're either serving stale data for hours or hammering your database on every request. Know the difference before you pick
the four strategies¶
SSR - Server-Side Rendering¶
HTML generated on every request. Fresh data always. Higher latency and server cost
// src/app/profile/page.tsx - SSR by default (no generateStaticParams)
export default async function ProfilePage() {
// This fetch runs on every request
const data = await fetch('https://api.example.com/user', {
cache: 'no-store' // forces SSR behavior
})
const user = await data.json()
return <div>{user.name}</div>
}
SSR is the default when you use cache: 'no-store' or when the page has dynamic functions like cookies() , headers() , or searchParams
Use SSR for: * Pages with user-specific data (dashboard , settings , profile) * Pages that need real-time data (scores , prices , status) * Pages behind auth where caching doesn't help
SSG - Static Site Generation¶
HTML generated at build time. Blazing fast. Stale until next build
// src/app/about/page.tsx - SSG by default
export default async function AboutPage() {
// This fetch runs at build time
const data = await fetch('https://api.example.com/about')
const content = await data.json()
return <div>{content.body}</div>
}
Without cache: 'no-store' or generateStaticParams , Next.js assumes the page can be statically generated. The HTML is generated during next build and served as a flat file
Use SSG for: * Marketing pages , landing pages , documentation * Public content that changes rarely * Pages where build-time data is acceptable
ISR - Incremental Static Regeneration¶
HTML generated at build time , revalidated in the background after a timeout
// src/app/blog/[slug]/page.tsx - ISR with revalidation
export default async function BlogPost({
params
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const data = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 } // revalidate every hour
})
const post = await data.json()
return <article>{post.title}</article>
}
ISR behavior: 1. First request after build serves the statically generated page 2. After revalidate seconds , the first request triggers a background regeneration 3. Subsequent requests see the new page 4. If regeneration fails , the old page is served (stale data but no downtime)
// on-demand revalidation - trigger from webhook or admin action
// src/app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache'
export async function POST(request: Request) {
const { path } = await request.json()
revalidatePath(path)
return Response.json({ revalidated: true })
}
Use ISR for: * Blog posts , news articles , product pages * Content that updates periodically but doesn't need real-time freshness * Pages where build-time generation is too slow but SSR is overkill
CSR - Client-Side Rendering¶
Empty HTML shell , React renders everything in the browser
'use client'
import { useEffect, useState } from 'react'
export default function Dashboard() {
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/dashboard-data')
.then(res => res.json())
.then(setData)
}, [])
if (!data) return <div>Loading...</div>
return <div>{data.metrics}</div>
}
Use CSR for: * Highly interactive dashboards with real-time updates * Pages behind auth where SEO doesn't matter * Components that need browser APIs
streaming with Suspense¶
Next.js supports streaming Server Components using React Suspense. Parts of the page load progressively instead of waiting for everything
// src/app/dashboard/page.tsx
import { Suspense } from 'react'
import SlowComponent from './SlowComponent'
import FastComponent from './FastComponent'
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<FastComponent /> {/* renders immediately */}
<Suspense fallback={<div>Loading slow data...</div>}>
<SlowComponent /> {/* streams in when ready */}
</Suspense>
</div>
)
}
// src/app/dashboard/SlowComponent.tsx
export default async function SlowComponent() {
const data = await fetch('https://api.example.com/slow-data', {
cache: 'no-store'
})
await new Promise(resolve => setTimeout(resolve, 3000)) // simulate slow response
const result = await data.json()
return <div>{result.value}</div>
}
Streaming improves perceived performance. The user sees the fast parts immediately while slow data loads in the background
picking the right strategy¶
| Strategy | Freshness | Speed | Server Cost | SEO |
|---|---|---|---|---|
| SSG | Build-time only | Fastest | Lowest | Best |
| ISR | Revalidate interval | Near-instant after first load | Low | Best |
| SSR | Every request | Depends on data source | Higher | Best |
| CSR | Every request | Fast (client-side) | None (client renders) | Worst |
The rule of thumb: default to SSG or ISR for public content , use SSR for user-specific pages , use CSR only for components that need browser APIs
prerequisites¶
next_04_navigation.md - you should understand client-side navigation and prefetching behavior
next → next_06_data_fetching.md