Work In Progress — this section is still being built.

SSR and Hydration for Angular at Scale

"Add SSR" usually means one of two very different things: server-side rendering a page on every request, or prerendering it once at build time and serving static HTML forever after. For an app where the content doesn't depend on the logged-in user — marketing pages, docs, a portfolio site like this one — prerendering wins on every axis that matters: there's no server-side render cost per request, hosting is trivial static file serving, and there's no live-SSR failure mode to monitor in production.

Hydration is the part that actually needs care

Whichever rendering mode you pick, the client still needs to pick up the server-rendered DOM and make it interactive without a visible flash or re-render. Angular's hydration does this by matching the DOM the server produced against what the client would have rendered, then attaching event listeners instead of tearing it down and rebuilding. A few things reliably break this:

  • Reading window/document/localStorage without an isPlatformBrowser() guard. These don't exist during the server render, so an unguarded read throws — or worse, silently returns something the client then has to reconcile.
  • Client-only state that changes the server-rendered output. A theme toggle that reads localStorage is the classic case: if you branch markup on it directly, the server renders one state, the client often has a different one, and hydration has to patch the mismatch instead of just claiming the existing nodes. The fix: set the attribute that controls it via CSS before Angular even boots (an inline script in index.html), so the visual state is correct pre-hydration regardless of what the signal says for that first tick.
  • Non-deterministic content — Math.random(), new Date() used for display, locale-dependent formatting that differs between the Node runtime and the browser.

@defer changes the hydration story too

Deferrable views mean part of the tree isn't rendered — or hydrated — until its trigger fires (on viewport, on interaction, on idle). That's a real performance win: less JS parsed and hydrated on first load. The trade-off is that you need a @placeholder sized close to the real content's height, or you get layout shift when the deferred block swaps in — which quietly undoes the Core Web Vitals win you were going for.

The result worth measuring

Done right, a prerendered Angular site ships close to static-site-generator levels of initial load performance while staying a real Angular application underneath — signals, standalone components, and router included — not a special "static mode" subset of the framework.

← Back to all posts

© 2026 Mohit Kumar Jain · Building scalable front-ends