skip to content

SSR & Prerendering

Angular's server rendering stack: @angular/ssr per-route render modes, non-destructive and incremental hydration, the HTTP transfer cache and prerendering. Interviewers probe hydration bugs.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

30

In Angular, what does provideClientHydration() change about how a server-rendered page starts in the browser, and where must it be provided?

level: juniorimportance: must knowfreq 60%

answer

  1. destroy and re-render vs reuse
  2. non-destructive
  3. server and client both
  4. ngh annotations in transfer state

basics

~10 s

provideClientHydration() makes Angular reuse the server-rendered DOM instead of discarding it and rendering again. It must be in the server bootstrap as well as the client, or no hydration data is sent.

solid answer

~40 s

Without hydration, an Angular app that was rendered on the server throws the server HTML away when it bootstraps and renders every component again, which can flicker and shift layout. `provideClientHydration()` from `@angular/platform-browser` enables non-destructive hydration: the server annotates its output (`ngh` attributes, serialized view data in transfer state, an integrity comment) and the client walks the existing DOM and reuses those nodes. The provider must be part of the server bootstrap too; with the default layout the server config is merged from the browser config, so one call covers both. If the client has it and the server does not, development mode logs NG0505 and the app renders from scratch. In v22 the same call also enables the HTTP transfer cache and incremental hydration by default.

code

ts · 15 lines
ts
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideServerRendering, withRoutes } from '@angular/ssr';
import { serverRoutes } from './app.routes.server';

export const appConfig: ApplicationConfig = {
  providers: [provideClientHydration()],
};

const serverConfig: ApplicationConfig = {
  providers: [provideServerRendering(withRoutes(serverRoutes))],
};

// The server config inherits provideClientHydration() from appConfig.
export const config = mergeApplicationConfig(appConfig, serverConfig);

go deeper

for a junior

Recall that provideClientHydration() comes from @angular/platform-browser and makes Angular reuse server HTML instead of rendering it again.

for a middle

Explain the annotations the server adds, why the provider must be in the server bootstrap too, and what NG0505 tells you.

for a senior

Know which defaults the call brings in v22, how to verify hydration in development, and how a missing server provider degrades silently to a full re-render.

for a principal

Weigh hydration's constraint that server and client DOM must match against its benefits, and decide how teams enforce hydration-safe components across a large codebase.

## Two ways a server-rendered page can start When an Angular page is rendered on the server, the browser first receives finished HTML. Then the JavaScript bundle loads and Angular bootstraps. What happens to the HTML that is already on screen depends on one provider: | Setup | What Angular does with the server HTML | Visible effect | | --- | --- | --- | | No `provideClientHydration()` | Discards it and renders every component from scratch | Possible flicker and layout shift | | `provideClientHydration()` in both configs | Walks the existing DOM and **reuses** the nodes, attaching listeners and bindings | No re-creation of DOM nodes | Angular calls the second path **non-destructive hydration**. It has been a stable public API since v17 (`provideClientHydration` is exported from `@angular/platform-browser`). ## Enabling it ```ts import { ApplicationConfig } from '@angular/core'; import { provideClientHydration } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { providers: [provideClientHydration()], }; ``` - `ng add @angular/ssr` adds this call to the root providers for you. - The **server** must include it too. With the default layout the server config is merged from the browser config (`mergeApplicationConfig`), so one call covers both. In a custom setup where the two configs share nothing, it must be added to the server side explicitly. - In an NgModule app, the call goes in the root module's `providers`. ## What the server adds when hydration is on When the server render has hydration enabled, it annotates its output so the client can match it: 1. component host elements receive an `ngh` attribute pointing at serialized view data; 2. that data travels to the browser inside the transfer state (under a `__nghData__` key); 3. a `<!--nghm-->` comment marks that the HTML has not been altered since rendering; 4. comment and text markers remain where Angular needs them to locate nodes. On the client, Angular checks for that serialized data. If it is present, hydration runs; if it is absent, hydration stays off and the app renders from scratch. ## What goes wrong when the two sides disagree - **Client has it, server does not.** In development Angular logs warning **NG0505** ("no hydration info in server response") and renders destructively. Routes served with `RenderMode.Client` are marked so this warning is suppressed, because there is nothing to hydrate. - **DOM changed between server and client.** Direct DOM manipulation, invalid HTML nesting or stripped comments produce the NG0500-family errors covered separately. ## What else the call switches on `provideClientHydration()` is not a single feature. In Angular 22.2 it also enables, by default: - the **HTTP transfer cache**, so `HttpClient` responses fetched on the server are reused instead of fetched again; - **incremental hydration**, which also turns on event replay; - in development, stability debugging that reports when the app takes too long to become stable. Each of those has its own opt-out feature function (`withNoHttpTransferCache()`, `withNoIncrementalHydration()`) and its own configuration story. For the full-app hydration question, the point is that one provider, present on both sides, is what turns "render twice" into "render once and reuse". ## How to confirm it works - In development the browser console prints hydration statistics: how many components and nodes were hydrated. - Angular DevTools can overlay which parts of the page were hydrated and highlight the component that caused a mismatch. ## A note on NgModule apps In an NgModule application the call goes in the root module's `providers`. Because the server module imports that root module in the default layout, the server side receives it automatically; a hand-built server module that does not import it needs its own call. ## Quick recap for an interview - Without the provider, server HTML is thrown away and rebuilt. - With it, the DOM is reused, which requires server and client to produce the same structure. - It must be present in the server bootstrap as well as the client, and it brings several related defaults with it in v22.

  • What happens if only the browser config includes provideClientHydration()?
    The server sends no hydration data, so the client keeps hydration off and renders from scratch. In development Angular logs NG0505 to say hydration was requested but the response had no serialized information. Adding the provider to the server bootstrap fixes it.
  • Is it harmful to keep provideClientHydration() for routes rendered only in the browser?
    No. A `RenderMode.Client` response carries no hydration data, so the client simply renders normally, and those pages are marked so the NG0505 warning is not logged.
  • How can you confirm hydration actually ran?
    In development the console prints hydration statistics with the number of components and nodes hydrated. Angular DevTools can overlay hydrated regions and highlight the component where a mismatch occurred.

Moving into a furnished flat: without hydration you empty every room and refurnish it; with hydration you keep the furniture and just plug in the appliances.

saying these in an interview costs you the question

  • Hydration is on by default in every server-rendered Angular app
  • provideClientHydration() is only needed in the browser config
  • Hydration makes the server HTML interactive before the bundle loads
  • Hydration re-renders the page on the client and then diffs it
  • provideClientHydration() only affects DOM reuse and nothing else in v22
open as a page

Why does an Angular component that reads window.innerWidth in its constructor crash during server-side rendering, and what are the server-safe alternatives?

level: juniorimportance: must knowfreq 66%

basics

~20 s

During SSR the component's constructor runs in Node, where window, localStorage and navigator do not exist, so the read throws. Move browser work into afterNextRender, reach the document through the DOCUMENT token, or guard browser-only logic with isPlatformBrowser(PLATFORM_ID).

open as a page

In an Angular app using @angular/ssr, how do you mark a route for build-time prerendering, and what does the build produce for it?

level: juniorimportance: must knowfreq 52%

basics

~10 s

Give the route RenderMode.Prerender in the server routes passed to provideServerRendering(withRoutes(...)). At build time Angular renders it once and writes a static index.html for that path, which is served as-is to every visitor.

open as a page

In Angular's @angular/ssr, what do RenderMode.Server, RenderMode.Client and RenderMode.Prerender do, and where do you assign them?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Each ServerRoute passed to withRoutes() sets a renderMode: Server renders HTML on every request, Prerender renders it once at build time, and Client returns the unrendered shell so the browser renders the page.

open as a page

In an Angular SSR app, why don't HttpClient GET requests made during the server render run again in the browser?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Angular's HTTP transfer cache records eligible HttpClient responses made during the server render, serializes them into the HTML, and replays them in the browser during hydration, so the same GET or HEAD request is not sent twice.

open as a page

In Angular, what does adding hydrate on viewport to a @defer block change on the server and in the browser?

level: middleimportance: must knowfreq 50%

basics

~20 s

The server renders the block's main content instead of its @placeholder. In the browser that HTML stays dehydrated, with its code not yet loaded, until the block enters the viewport; then Angular fetches the code and hydrates it in place.

open as a page

In Angular SSR, when do you use afterNextRender, when isPlatformBrowser with PLATFORM_ID, and when separate server and browser providers?

level: middleimportance: must knowfreq 55%

basics

~20 s

Use afterNextRender for DOM work, since it runs only in the browser after rendering. Use isPlatformBrowser(PLATFORM_ID) for small non-rendering branches. When a whole service behaves differently per platform, provide separate implementations in the browser and server configs.

open as a page

An Angular SSR build fails after adding a docs/:slug route; why, and how does getPrerenderParams let you prerender each documentation page?

level: middleimportance: must knowfreq 48%

basics

~20 s

The default '**' Prerender server route now matches docs/:slug, and the build cannot guess slug values, so it fails. Add a server route for docs/:slug with getPrerenderParams returning objects like { slug: 'routing' }; the build renders one page per object.

open as a page

In Angular 22, how is incremental hydration enabled or disabled, and what does it switch on implicitly?

level: juniorimportance: should knowfreq 40%

basics

~10 s

In Angular 22 provideClientHydration() enables incremental hydration by default; withNoIncrementalHydration() opts out, and withIncrementalHydration() is deprecated. It also enables event replay, so withEventReplay() is redundant.

open as a page

In Angular hydration, what does withEventReplay() do with clicks made before hydration finishes, and do you still need it in Angular 22?

level: middleimportance: should knowfreq 40%

basics

~10 s

withEventReplay() captures native events such as clicks that happen before hydration and replays them once listeners are attached. In Angular 22 it is on by default, because default incremental hydration enables it.

open as a page

In Angular hydration, what does the ngSkipHydration attribute do, where may you place it, and what does using it cost?

level: middleimportance: should knowfreq 45%

basics

~10 s

ngSkipHydration makes Angular skip hydrating one component and its children, which then re-render from scratch on the client. It is allowed only on a component host element and costs hydration's benefits in that subtree.

open as a page

In Angular, for a block written @defer (on idle; hydrate on interaction), which trigger applies when, and why keep its @placeholder?

level: middleimportance: should knowfreq 30%

basics

~10 s

hydrate on interaction applies only on the initial server-rendered load; after client-side navigation the block uses on idle and shows its @placeholder first. The placeholder is therefore still required for the client path.

open as a page

In Angular, why inject the DOCUMENT token instead of using the global document, and what happens to changes made through it during SSR?

level: middleimportance: should knowfreq 44%

basics

~20 s

DOCUMENT gives the platform's document through DI: the real one in the browser and a per-request server DOM during SSR. Changes made through it, such as head tags, are serialized into the HTML, while the global document does not exist on the server.

open as a page

In Angular SSR, what happens when a visitor requests a docs page that getPrerenderParams did not list, and how do PrerenderFallback.Server, Client and None differ?

level: middleimportance: should knowfreq 34%

basics

~20 s

The route's fallback decides. PrerenderFallback.Server, the default, renders the page on the server at request time; Client sends the client-rendered shell; None makes Angular's engine decline the request, so the next handler, usually a 404, answers.

open as a page

In @angular/ssr, how do AngularNodeAppEngine and AngularAppEngine differ, and what does handle() return for a URL Angular does not own?

level: middleimportance: should knowfreq 30%

basics

~10 s

AngularAppEngine takes a Web Request and returns a Response; AngularNodeAppEngine accepts Node requests too and converts them. Both resolve to null for a non-Angular URL so the server can call the next handler.

open as a page

In an Angular SSR app, how do you make the not-found page answer HTTP 404, and when is inject(RESPONSE_INIT) null?

level: middleimportance: should knowfreq 45%

basics

~20 s

Either give the server route a static status: 404, or inject RESPONSE_INIT in the not-found component and set status to 404. RESPONSE_INIT, like REQUEST, is null in the browser, when prerendering, and for Client routes.

open as a page

With Angular's HTTP transfer cache, which requests are skipped by default, and how do withHttpTransferCacheOptions and the transferCache request option change that?

level: middleimportance: should knowfreq 42%

basics

~10 s

By default only GET and HEAD are cached, minus requests with auth headers, cookies or credentials and responses marked no-store, no-cache, private or carrying Set-Cookie. withHttpTransferCacheOptions relaxes these globally; transferCache adjusts one request.

open as a page

In Angular SSR, how do you use TransferState and makeStateKey to carry data that did not come through HttpClient from the server to the browser?

level: middleimportance: should knowfreq 45%

basics

~20 s

Create a typed key with makeStateKey, inject TransferState, set the value during the server render, and in the browser read it with get or hasKey before recomputing. Values travel as JSON inside the page, so keep them small, plain and non-secret.

open as a page

On a long server-rendered Angular product page, which hydrate triggers would you give the add-to-basket panel, the comments section and the legal footer, and why?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Choose by when each block must respond: hydrate on idle or interaction for the add-to-basket panel, hydrate on viewport for comments far below the fold, and hydrate never for a static footer.

open as a page

An Angular SSR app reads the user's saved theme from localStorage; how do you apply it without crashing the server render or breaking hydration?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Render the same default theme on server and browser, then read localStorage only inside afterNextRender and set a class on the html element through DOCUMENT. To avoid a flash of the default, keep the choice in a cookie the server can read.

open as a page

In Angular SSR, why can a page be sent without data loaded by a third-party SDK promise, or never finish rendering, and how does PendingTasks help?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The server sends HTML once the app is stable, with no pending tasks left. A zoneless app ignores untracked work like an SDK promise, so HTML ships early; a task never released hangs the render. PendingTasks registers such work.

open as a page

Which Angular routes should not use RenderMode.Prerender, and what goes wrong when a request-dependent page is prerendered?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Pages that depend on the visitor, cookies, headers, query strings or fast-changing data should not be prerendered. At build time REQUEST is null, so the page renders its anonymous, default version once, and every visitor receives it until the next build.

open as a page

Why does Angular's HTTP transfer cache skip requests with Authorization or Cookie headers by default, and when is includeRequestsWithAuthHeaders safe to enable?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Authenticated responses are user-specific, and the transfer cache embeds the whole response body in the HTML. If that HTML is cached or prerendered, one user's data reaches others. Enable the flag only for per-request, non-shared pages, narrowed with filter.

open as a page

An Angular SSR news homepage still requests its headlines twice, on the server and again after hydration; how would you find out why the transfer cache missed?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Check whether the server stored the response in the ng-state script, then compare the browser request with the server one: a different origin or params, an auth header, credentials, uncacheable Cache-Control or Set-Cookie, a late request after stability, or a non-HttpClient call all cause misses.

open as a page

For a large Angular app with marketing pages, a product catalogue and a signed-in account area, how would you assign @angular/ssr render modes per route?

level: principalimportance: should knowfreq 30%

basics

~20 s

Prerender pages that are identical for everyone and must rank, render per request (Server) pages that depend on the request or change often, and use Client for per-user areas nobody indexes, choosing the ** default deliberately.

open as a page

How does Angular deliver a route with redirectTo when the app is prerendered for static hosting, compared with when Angular's server engine serves it?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

With static output, the build writes an HTML page for the old path containing a zero-delay meta refresh and a link: a soft redirect served with status 200. Behind Angular's server engine, the same route gets a real HTTP redirect, 302 by default.

open as a page

An Angular app hydrates cleanly in development but throws NG0507 after deployment behind a CDN. What is Angular detecting, and what else alters server HTML?

level: seniorimportance: nice to knowfreq 30%

basics

~10 s

NG0507 means the server's hydration integrity comment is missing, so something rewrote the HTML in transit, typically CDN minification stripping comments and whitespace. Disable it; also keep templates valid HTML and whitespace settings identical.

open as a page

In Angular incremental hydration, why does a nested @defer block's hydrate when condition never fire while its parent block is dehydrated?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

Hydration runs from the top-most dehydrated @defer block down, and a hydrate when condition belongs to the parent's template, which is not live while dehydrated. So hydrate when works only on the top-most dehydrated block.

open as a page

After an upgrade, an Angular SSR app fails every request with NG0401 Missing Platform. What is BootstrapContext, and how must main.server.ts change?

level: seniorimportance: nice to knowfreq 25%

basics

~10 s

BootstrapContext carries the platformRef the server created for this request. The server bootstrap function must accept it and pass it as the third argument: bootstrapApplication(App, config, context).

open as a page