Server-Side Rendering Strategies
Choose and implement the right rendering strategy for performance and SEO. Covers SSR, SSG, ISR, streaming SSR, edge rendering, hydration patterns, and the patterns that deliver fast, SEO-friendly web applications.
How and where your HTML is rendered determines your page load speed, SEO ranking, and user experience. The wrong rendering strategy can mean a 3-second blank page (bad CSR), stale content (bad SSG), or expensive compute (bad SSR). Choosing the right strategy per page is the key to performance.
Rendering Strategies Compared
Client-Side Rendering (CSR):
Where: Browser
When: On every page load
HTML: Empty shell, JS builds the page
First Paint: Slow (download JS → execute → render)
SEO: Poor (search engines may not execute JS)
Server Cost: Low (serve static files)
Best for: Dashboards, admin panels, authenticated pages
Server-Side Rendering (SSR):
Where: Server
When: On every request
HTML: Complete HTML sent to browser
First Paint: Fast (HTML arrives ready)
SEO: Excellent (full HTML for crawlers)
Server Cost: High (render per request)
Best for: Dynamic content that changes per user/request
Static Site Generation (SSG):
Where: Build server
When: At build time
HTML: Pre-built HTML files
First Paint: Fastest (serve pre-built files from CDN)
SEO: Excellent (full HTML)
Server Cost: Lowest (no compute at request time)
Best for: Blogs, docs, marketing pages, content that rarely changes
Incremental Static Regeneration (ISR):
Where: Edge / Server
When: On first request after revalidation period
HTML: Pre-built, regenerated in background
First Paint: Fast (serve stale, regenerate in background)
SEO: Excellent
Server Cost: Low (regenerate only when stale)
Best for: Product pages, blog posts, content that changes periodically
Streaming SSR
// Next.js App Router: Streaming SSR with Suspense
// Server sends HTML in chunks as data becomes available
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function DashboardPage() {
return (
<div>
{/* Header renders immediately */}
<h1>Dashboard</h1>
{/* Revenue streams in when data is ready */}
<Suspense fallback={<RevenueSkeleton />}>
<RevenueChart /> {/* Server component - fetches data */}
</Suspense>
{/* Orders stream independently */}
<Suspense fallback={<OrdersSkeleton />}>
<RecentOrders /> {/* Another server component */}
</Suspense>
</div>
);
}
// Server Component: Fetches data on server, streams HTML when ready
async function RevenueChart() {
const data = await fetchRevenueData(); // Slow API call (2 seconds)
return (
<div className="chart">
{data.map(point => (
<Bar key={point.month} value={point.revenue} />
))}
</div>
);
}
// Timeline:
// 0ms: Shell HTML sent (header + skeletons)
// 200ms: Orders data ready → HTML chunk streamed
// 2000ms: Revenue data ready → HTML chunk streamed
//
// Without streaming: User waits 2000ms for anything
// With streaming: User sees layout immediately, data fills in
Per-Page Strategy
Page Type | Strategy | Revalidation
---------------------|----------|-------------
Homepage | ISR | Every 60 seconds
Blog post | SSG | On content change (webhook)
Product page | ISR | Every 5 minutes
Search results | SSR | Every request
User dashboard | CSR | Client-side
Checkout flow | SSR | Every request (no caching)
API documentation | SSG | On deploy
Pricing page | SSG | On deploy
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| CSR for SEO-critical pages | Poor search rankings | SSR or SSG for public pages |
| SSR for static content | Unnecessary compute cost | SSG or ISR for content pages |
| No loading skeletons | Layout shift, poor UX | Suspense boundaries with skeletons |
| Full page SSR (no streaming) | Slowest component blocks entire page | Streaming SSR with Suspense |
| SSG with frequent rebuilds | Long build times, stale content | ISR for frequently changing content |
The best applications use different rendering strategies for different pages. A blog post does not need SSR. A dashboard does not need SSG. Match the strategy to the page.