SSR and Incremental Hydration In short: Building the page on the server went through three generations in four years. Knowing which one a project uses explains most of how fast it feels. Generation 1: throw it all away Angular built the page on the server and sent it as HTML. The browser showed it. Then Angular started up and threw the whole thing away , rebuilding it from scratch. The user saw content quickly, then saw it flash and redraw. The layout jumped, scroll position could move, and anything focused was lost. First paint looked great in a test. The real experience was worse than a normal app for anyone who tried to interact early. Generation 2: reuse what is there (v16) Angular now walks the existing page and attaches to it — matching its components against the elements already there, adding event listeners, and leaving everything in place. No flash, no jump, no lost scroll position. This was a big improvement and is the baseline expectation now. But every component still starts up immediately, which means all of your app's code downloads and runs before anything works. A page with a heavy footer, a comments section, and three widgets below the fold pays for all of them up front. When the two versions do not match This only works if the browser's first render produces the same structure the server produced. When it does not, you get an error and Angular falls back to throwing that section away. The four causes, roughly in order of how often they happen: Invalid HTML nesting. The browser quietly repairs broken HTML while reading it, so what the browser has is not what the server sent: Changing the page directly before startup. Setting in a constructor changes the page underneath the process. Use for anything that touches the page directly. Browser-only code during rendering. , , and do not exist on the server: Values that change every time. and produce different results on each render. Debug with , which logs detailed information about what did not match. Generation 3: start up only what is needed (v19 preview, v20 final, v22 default) The insight: startup does not have to be all at once. A component can stay as plain HTML — visible, styled, no code downloaded — until something makes it worth waking up. From v22 this is on by default. Opt out with . Then mark the areas: The difference from a normal matters a lot: — the server sends the placeholder . The real content only appears after the trigger. Nothing useful is in the HTML. — the server sends the real content . It is in the HTML immediately, visible and findable by search engines. Only the code waits. That second one is what you want for content that should exist for search engines and for users who never scroll far enough to need it working. The hydrate triggers mirror the normal ones: deserves attention. For genuinely static content — an article body, a footer, a terms page — the HTML is sent from the server and the code for that section is never downloaded. Not delayed. Never. Choosing a mode per route (v19, final v20) Not every page wants the same approach. A marketing page should be built once ahead of time. A dashboard behind a login should not be built on the server at all. — built once when you build the app. Fastest possible. Only for content that is the same for everyone. — built for each visit. For personal or fast-changing content that still needs to be findable. — not built on the server at all. Right for anything behind a login, where server rendering adds delay for content no search engine will ever see. Choosing per route is the whole point. Most real apps need all three. Reusing server data (and why v22 matters here) Without help, a server-rendered page fetches its data twice — once on the server to build the page, once in the browser afterwards. The user sees content, then sees it reload. Requests made while rendering on the server are saved into the HTML and reused by the browser automatically. From v22, does this too — data loaded on the server is picked up on the browser's first draw with no second fetch and no flash. That is a real simplification. The old "load it in a resolver, stash it, read it back" pattern is now just using normally. Replaying clicks A user can click during the gap between the HTML appearing and the code being ready. Without help, that click is lost. Angular records events that happen before startup and replays them once the component is alive. This is on by default from v18, and it is essential with incremental hydration — the whole model assumes components sit dormant while the page looks usable. "But isn't server rendering just for search engines?" That was the original reason, and it is now the smaller half. The bigger half is what the user sees while your code is still loading. A browser-only app shows nothing until its code downloads, runs, and makes its first request. On a cheap phone over a slow connection, that blank period is long. Server rendering puts real content on screen before any of your…