What is Server-Side Rendering (SSR)?
Server-Side Rendering (SSR) is a web architecture pattern that generates HTML on the server for each incoming request.
What is Server-Side Rendering (SSR)?
Server-Side Rendering (SSR) is a web architecture pattern where the server generates the full HTML for a page on every incoming request, then sends that markup to the browser. Unlike client-side rendering (CSR), where the browser downloads a JavaScript bundle and builds the DOM locally, SSR delivers a complete document that's immediately parseable by search engines and screen readers. The technique predates the SPA era — traditional PHP, Rails, and Django apps were SSR by default — but it became a distinct architectural choice again around 2016 when Next.js (v1, October 2016) reintroduced it for React apps. SSR typically improves Largest Contentful Paint (LCP) because the browser paints meaningful content before any JS executes. The trade-off is higher server compute per request compared to pre-built static pages. We reach for SSR on projects where content depends on the request — authenticated dashboards, personalized storefronts, or pages with real-time data that can't be statically generated at build time.
How it works
When a user (or a bot) requests a URL, the SSR server runs the page's component tree, fetches any required data, and produces an HTML string. That string is sent as the HTTP response with a Content-Type: text/html header. Here's a simplified Next.js App Router example:
// app/products/[slug]/page.tsx
export const dynamic = 'force-dynamic'; // opt into SSR
export default async function ProductPage({ params }: { params: { slug: string } }) {
const product = await fetch(`https://api.example.com/products/${params.slug}`, {
cache: 'no-store',
}).then(res => res.json());
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span>${product.price}</span>
</main>
);
}
The cache: 'no-store' directive tells Next.js (v14+/v15) not to cache the fetch, so data is fresh on every request. After the HTML arrives, the browser downloads the JS bundle and hydrates the page — attaching event listeners and making it interactive. In Astro (v4+), SSR is enabled by setting output: 'server' in astro.config.mjs, and components only ship JS if you explicitly add a client:* directive, which keeps the hydration payload small.
On the infrastructure side, SSR requires a running server or serverless function (AWS Lambda, Vercel Functions, Cloudflare Workers). Response times depend on data-fetch latency and render complexity. Techniques like streaming SSR (React 18's renderToPipeableStream) let the server flush HTML in chunks, so the browser can start painting before the full response completes — this measurably improves Time to First Byte (TTFB) on data-heavy pages.
When to use it
SSR is the right call when the HTML must reflect request-specific data that can't be known at build time. Concrete scenarios:
Yes: Authenticated pages (dashboards, account settings) where content varies per user session.
Yes: E-commerce product pages with real-time inventory or pricing pulled from a headless CMS or PIM.
Yes: Search results pages where query params drive the content and you still need crawlable HTML.
Yes: Pages behind A/B tests that need different markup per visitor without client-side flicker.
No: Marketing pages, blog posts, docs — use Static Site Generation (SSG) or Incremental Static Regeneration (ISR) instead. Faster, cheaper, cacheable at the CDN edge.
No: Highly interactive apps (spreadsheets, design tools) where the initial HTML shell is trivial — CSR with a loading skeleton is often simpler.
No: When your data changes less than once per minute and you can tolerate stale-while-revalidate — ISR gives SSR-like freshness without per-request compute.
We've shipped SSR on 50+ projects. Our rule of thumb: if you can statically generate it, do. SSR is for the pages that truly can't be.
SSR vs alternatives
| Criteria | SSR | SSG / ISR | CSR (SPA) | Edge SSR |
|---|---|---|---|---|
| HTML generated | Per request, on server | At build time (or revalidated) | In browser | Per request, at edge |
| TTFB | Medium (server + data fetch) | Fast (CDN-cached) | Fast (small shell) | Fast (close to user) |
| LCP | Good | Excellent | Poor without prerender | Good |
| SEO friendliness | Excellent | Excellent | Weak without prerender | Excellent |
| Server cost | Higher (compute per req) | Low (static files) | None (client only) | Medium (edge compute) |
| Data freshness | Real-time | Stale until rebuild/revalidate | Real-time (client fetch) | Real-time |
Edge SSR (Cloudflare Workers, Vercel Edge Functions) is a hybrid: it runs SSR logic on edge nodes closer to users, reducing TTFB. The constraint is runtime — you're limited to V8 isolates, no Node.js APIs. Our preferred stack is Next.js 15 on Vercel with edge middleware for auth checks and Node.js SSR for data-heavy pages.
Real-world example
We built a headless Shopify storefront for a DTC brand using Next.js 15 App Router. Product listing pages used ISR with a 60-second revalidation window — good enough for SEO, low compute cost. But the cart page and checkout flow required SSR because they depended on session cookies and real-time inventory checks from Shopify's Storefront API. After switching those routes from CSR to SSR, the cart page's LCP dropped from ~3.2s to ~1.4s on mobile (measured via CrUX data over 28 days). Googlebot indexed the cart-adjacent pages correctly for the first time, and the brand saw a 12% lift in organic landing page impressions for transactional queries. Astro would've been a solid choice here too, but the team already had React expertise, so Next.js was the pragmatic pick.