"Server-side rendering" gets used as a catch-all for several distinct techniques. Understanding where each one actually happens — and what it costs — makes it much easier to reason about a real Next.js app's performance.
Classic SSR: HTML generated per request
In its original form, SSR means a server runs your component tree on every request and returns finished HTML. The browser gets something to paint immediately, then "hydrates" — attaching event listeners to that existing markup — before the page becomes interactive.
React Server Components change what gets sent at all
Server Components go further: they never ship their JavaScript to the browser at all. Only the rendered output crosses the network. A component that fetches data and renders a list can do so entirely on the server, with zero client-side bundle cost, as long as it doesn't need interactivity.
// This never ships to the browser as JS — only its rendered HTML does.
export default async function ProductList() {
const products = await db.product.findMany();
return (
<ul>
{products.map((p) => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
Streaming: not waiting for the slowest part
Streaming SSR lets the server send finished HTML for fast parts of a page immediately, while slower data-dependent sections stream in afterward — via <Suspense> boundaries — instead of the whole response waiting on the slowest query.
Static vs. dynamic: the actual trade-off
A statically rendered page is built once (at build time or on first request, then cached) and served identically to everyone — fastest possible response, but the content can go stale. A dynamically rendered page re-runs on every request — always current, at the cost of doing real work per visitor. Next.js lets you choose per-route, and per-fetch via revalidate, rather than forcing one strategy for an entire app.



