Server-Side Rendering in Next.js: How It Works and When to Use It
Server-side rendering (SSR) is one of the core rendering strategies in Next.js, and it’s often the most misunderstood. Sometimes used to represent “anything that isn’t a static page,” sometimes confused with React Server Components. This post explains what SSR actually does at the protocol level, how Next.js implements it, and when it’s the right rendering choice versus the alternatives.
What Server-Side Rendering Actually Means
In a client-side rendered (CSR) application, the server sends a near-empty HTML document and a JavaScript bundle. The browser downloads the bundle, executes it, fetches data, and only then paints the actual content. The user stares at a blank page or a spinner until that chain completes.
SSR inverts the order of operations. On each request, the server runs the React component tree, fetches the data the page needs, and renders the full HTML document before sending anything to the browser. The response that hits the network is already a complete HTML page — indexable, and visible without waiting on JavaScript execution.
The browser still downloads and connects the JavaScript afterwards, in a process called hydration, which attaches event listeners and React’s internal state to the existing HTML so the page becomes interactive. The critical difference is that content is visible before hydration finishes, not after.
This matters most for two metrics: First Contentful Paint (FCP) and Largest Contentful Paint (LCP), both of which measure how quickly meaningful content appears on screen — not how quickly the page becomes interactive. SSR directly targets these.
How Next.js Implements SSR
In the App Router, any component is a Server Component by default. Server Components run exclusively on the server, never ship their code to the browser, and can read data directly without a client-side fetch:
// app/products/[id]/page.tsx
async function ProductPage({ params }: { params: { id: string } }) {
const response = await fetch(`api/products/${params.id}`);
const product = await response.json();
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span>${product.price}</span>
</div>
);
}
export default ProductPage;The function runs on the server for every request, and the data fetch is just an await in the component body. Next.js renders this to HTML and sends it back in the response. This is the default behavior of the App Router, not an opt-in mode. A page becomes dynamically rendered the moment it reads something request-specific: cookies, headers, search params, or a fetch call.
The older Pages Router pattern, still found in production codebases, makes this explicit with a dedicated data-fetching function:
// pages/products/[id].tsx
export async function getServerSideProps({ params }) {
const response = await fetch(`api/products/${params.id}`);
const product = await response.json();
return { props: { product } };
}
export default function ProductPage({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span>${product.price}</span>
</div>
);
}Functionally, both approaches do the same thing: execute on the server, on every request, before the response is sent.
SSR vs the Other Rendering Modes
Next.js supports four rendering strategies, and the right choice depends on how often the underlying data changes and how personalized the page is.
Static Site Generation (SSG) renders pages at build time. The HTML is generated once and served identically to every visitor from a CDN edge node. This is the fastest possible response because there’s no server computation per request — but it only works for content that doesn’t change or changes rarely enough that a rebuild is acceptable.
Incremental Static Regeneration (ISR) extends SSG by allowing a static page to be regenerated in the background after a defined interval, without a full rebuild. Visitors get the fast, cached static response, while the underlying content stays reasonably fresh. This is the common middle ground for content like product catalogs or marketing pages that update periodically but don’t need computation for each request.
Server-Side Rendering (SSR) trades that caching advantage for correctness. Every request runs the render fresh — a logged-in user can see their own cart with current items and prices, a dashboard can show current numbers, or a search results page can reflect a live query.
Streaming SSR addresses SSR’s main weakness: a slow data dependency blocking the entire response. Using Suspense boundaries, Next.js can send the parts of the page that are ready immediately and stream in slower sections as their data resolves, rather than holding the whole response hostage to the slowest query:
import { Suspense } from "react";
export default function Dashboard() {
return (
<div>
<Header />
<Suspense fallback={<SkeletonStats />}>
<SlowAnalyticsWidget />
</Suspense>
</div>
);
}The header and layout render and reach the browser immediately. The analytics widget shows a skeleton while its data resolves, and streams in once ready — without making the user wait on a blank screen for the slowest part of the page.
Where SSR Creates Real Cost
SSR is not free, and it’s worth being specific about where the cost shows up — because this is where a lot of production sites get into trouble.
Server compute scales with traffic. A static page costs nothing extra to serve to the millionth visitor. An SSR page runs the render function on every single request, which means traffic spikes translate directly into server load. A page that’s fine at 50 requests per second can fall over at 500 if the render function is doing expensive work — unoptimized database queries, N+1 fetches, or unindexed lookups, all of which get multiplied by request volume in a way they wouldn’t on a static or ISR page.
Time to First Byte (TTFB) is bounded by the slowest server-side dependency. If a page’s data fetch hits a slow API, an unoptimized query, or an external service with high latency, that latency shows up directly in the response time for every visitor, every time. Streaming SSR mitigates this for parts of a page, but the initial shell is still rendered on the server.
Caching gets harder. Static and ISR pages are cacheable by definition — same input, same output, served from the edge. SSR pages, by design, often can’t be cached the same way, since the entire point is that the response reflects current request-time state. This pushes more load onto the servers and database connections that a cache layer would otherwise absorb.
None of this is an argument against SSR — it’s an argument for matching the rendering strategy to the actual requirement. A marketing homepage doesn’t need SSR. A live ecommerce cart page might. The most common mistake is applying SSR to pages that do not need request-time freshness, because as traffic increases, the computational load on the server does too.
The Practical Takeaway
SSR solves a specific problem: getting accurate, request-specific content to the browser as fast as possible, with meaningful content visible before JavaScript finishes loading. It’s the right tool when data is genuinely dynamic and personalization matters. It’s the wrong tool — and an expensive one — when applied indiscriminately to content that could be static or incrementally regenerated instead.
The rendering strategy is also only half the equation. SSR’s response time ceiling is set by whatever it’s calling — APIs, databases, search indexes. The architecture behind the render function matters as much as the render function itself. A page can be using the “correct” rendering strategy on paper and still be slow in production, because the bottleneck was never the rendering layer to begin with.
Frequently asked questions
What is hydration in the context of server-side rendering?
After SSR sends complete HTML to the browser, hydration is the process where the JavaScript bundle downloads and attaches event listeners and React’s internal state to the existing markup. Content is visible before hydration finishes — that’s the key difference from CSR.Why does SSR make caching harder than SSG or ISR?
SSR pages reflect current request-time state by design, so same-input/same-output CDN caching isn’t possible the same way. This pushes more load onto servers and database connections that a cache layer would otherwise absorb on a static or ISR page.What is the difference between SSR and SSG in Next.js?
SSG renders at build time and serves from a CDN edge — no per-request compute, maximum speed. SSR renders on the server for every request, which adds latency but ensures the response reflects current, personalized, or request-specific data.Does Next.js use server-side rendering by default?
In the App Router, Server Components run on the server by default, but pages are statically rendered unless they access request-specific data like cookies, headers, or dynamic fetches — at which point Next.js switches to dynamic (SSR) rendering automatically.
Let's Talk Architecture
If your systems are starting to limit growth – or you want to avoid that happening – we're open to a technical conversation.
Free Strategy Call
No pressure.
Just architecture.
