Server-side rendering: HTML first, JavaScript second
Server-side rendering means the server runs your components and sends back real,
filled-in HTML on the first response — a page a browser can paint immediately and
a crawler can read — instead of an empty <div id="root"> that only becomes a
page after a JavaScript bundle downloads and runs. That one change ripples
outward: it changes what the user sees at the first paint, what a search engine
indexes, and what work your server has to do on every request. SSR is not “better
than” client rendering; it moves the rendering cost from the user’s device to
your server, and that trade is right for some pages and wrong for others.
The server renders the tree to a string
The mechanism is a render-to-string call on the server: your component tree is executed and serialised to HTML, sent as the response body, and the client attaches to it later. In a React app the two ends look like this:
// server: run the app and stream/serialise it into the HTML response
import { renderToString } from "react-dom/server";
app.get("*", (req, res) => {
const html = renderToString(<App url={req.url} />);
res.send(`<!doctype html><div id="root">${html}</div><script src="/client.js">`);
});
// client: attach to the existing markup instead of creating it from scratch
import { hydrateRoot } from "react-dom/client";
hydrateRoot(document.getElementById("root"), <App url={location.pathname} />);
hydrateRoot, not createRoot: the DOM already exists, and the client’s job is to
adopt it and wire up the event handlers.
What you gain, and what it costs
The gains are real: a faster first contentful paint (there is content in the
first byte), correct SEO and social previews (crawlers see filled HTML without
running JS), and a usable page on slow devices sooner. The costs are equally real:
your server now does render work on every request (CPU and latency you must cache
around), your components must run in a Node environment where window does not
exist, and there is a window where the page looks ready but is not yet
interactive — the gap before hydration finishes.
// SSR code runs where there is no DOM — guard browser-only access
const isBrowser = typeof window !== "undefined";
const width = isBrowser ? window.innerWidth : DEFAULT_WIDTH; // no crash on the server
Choose SSR where the first view matters to a stranger
The clean heuristic: SSR earns its cost on pages whose first view matters to someone who is not signed in — marketing pages, articles, product listings, anything a crawler or a first-time visitor lands on. It earns less on a deeply interactive, authenticated app behind a login, where SEO is irrelevant and the user will wait for the bundle once. Modern frameworks blur the line with streaming SSR and partial hydration, but the underlying trade never changes: you are choosing to spend server work to improve the first view. The render-strategy-choice exercise makes you pick per page against real constraints, which is the actual skill — not memorising that SSR exists, but knowing when its cost buys you something.