Server-Side Rendering (SSR)
Server-Side Rendering generates HTML on every request. The page is always fresh but requires server compute.
When Pages Become Dynamic
Pages render dynamically when they:
- Use
cookies()orheaders() - Access
searchParams - Use
cache: 'no-store'in fetch - Set
export const dynamic = 'force-dynamic'
import { cookies } from 'next/headers'
// This page renders on every request
export default async function Dashboard() {
const cookieStore = await cookies()
const session = cookieStore.get('session')
const userData = await fetch('https://api.example.com/user', {
headers: { Authorization: `Bearer ${session?.value}` }
}).then(res => res.json())
return <DashboardView user={userData} />
}
Forcing Dynamic Rendering
Explicitly opt into SSR:
// Force dynamic rendering
export const dynamic = 'force-dynamic'
export default async function Page() {
// Always renders at request time
const data = await fetch('https://api.example.com/realtime')
.then(res => res.json())
return <div>{data.value}</div>
}
Using searchParams
Access URL query parameters:
// URL: /search?q=nextjs&page=2
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string; page?: string }>
}) {
const params = await searchParams
const query = params.q || ''
const page = parseInt(params.page || '1')
const results = await search(query, page)
return <SearchResults results={results} />
}
Any page using searchParams is automatically dynamic.
Streaming with Dynamic Pages
SSR pages can still stream content:
import { Suspense } from 'react'
export default async function DynamicPage() {
const user = await getCurrentUser() // Fast
return (
<div>
<h1>Welcome, {user.name}</h1>
<Suspense fallback={<Loading />}>
<SlowAnalytics userId={user.id} />
</Suspense>
</div>
)
}
The page structure shows immediately; slow parts stream in.
SSR vs API Routes
You don't need API routes for SSR data:
// ❌ Over-engineering: Creating an API route just for internal data
// app/api/user/route.ts + client-side fetch
// ✅ Direct: Just fetch in the Server Component
export default async function Page() {
const user = await db.users.findUnique({ where: { id: userId } })
return <Profile user={user} />
}
Performance Considerations
SSR has tradeoffs:
| Pros | Cons |
|---|---|
| Always fresh data | Server compute per request |
| Works with authentication | Higher latency than static |
| Access to request context | Can't be cached on CDN |
Optimizing SSR
- Use Suspense for slow data
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
- Parallel data fetching
const [user, posts] = await Promise.all([
getUser(),
getPosts()
])
- Consider ISR for semi-dynamic data
export const revalidate = 60 // Fresh enough, but cached
Summary
- Pages become dynamic when using
cookies(),headers(), orsearchParams - Use
force-dynamicto explicitly opt into SSR - SSR provides fresh data but requires server compute
- Use Suspense to stream slow parts of dynamic pages
- Consider ISR if data doesn't need to be real-time

