Open-Source Wikis

/

Angular

/

Features

/

Server-side rendering

angular/angular

Server-side rendering

Rendering Angular templates in Node.js, sending the resulting HTML as the server response, and (with hydration enabled) reusing that HTML at client boot. Spans @angular/platform-server, @angular/ssr, and @angular/build (in the CLI repo).

What it covers

Angular SSR supports four delivery modes:

  • SSR (server-side rendering at request time). A request hits a Node handler that renders fresh HTML and returns it.
  • SSG (static site generation). At build time the framework renders all known routes and writes them as static files.
  • Prerendering. A subset of SSG: a list of routes is prerendered while everything else falls back to client rendering.
  • Hybrid (SSR + hydration). The default. Server HTML is reused at client boot via hydration annotations.

High-level pipeline

graph LR
  Request["HTTP request"] --> Handler["Server handler<br/>(Express, Hono, Vercel, ...)"]
  Handler --> Render["renderApplication()"]
  Render --> Domino["DOM via Domino"]
  Domino --> Boot["bootstrapApplication on server"]
  Boot --> Tick["tick() until stable"]
  Tick --> Serialize["Serialize DOM + TransferState"]
  Serialize --> Response["HTML response"]
  Response --> Browser
  Browser --> Hydrate["provideClientHydration() walks DOM"]
  Hydrate --> Live["Live, interactive app"]

renderApplication lives in packages/platform-server/src/render.ts. It builds an Angular application against domino-backed DOM, runs change detection until the application reaches stability (ApplicationRef.isStable becomes true), then serializes the result.

Stability

A render isn't sent until the app is stable. Stability means no microtasks, macrotasks, or Pending Tasks are outstanding:

  • Pending Tasks (packages/core/src/pending_tasks.ts) — explicit "don't ship the response yet" tokens, used by HTTP and signals to indicate in-flight work.
  • Zone tasks (in zone mode) or scheduler emptiness (in zoneless mode).

If your component spawns long-running async work, use pendingTasks.add() to keep SSR waiting; otherwise the response will go out before the work finishes.

Hydration

The companion to SSR: instead of throwing away the server-rendered DOM, the client matches it against template execution and reuses each node. See systems/hydration for the full flow. Activated by provideClientHydration() on the client side.

platform-server emits the matching annotations (data attributes plus a <script> containing TransferState JSON) that hydration consumes.

TransferState

State that should not be re-fetched on the client:

  • HttpClient requests made on the server are cached under synthetic keys; the client HttpClient reads from the cache before issuing real requests.
  • Custom data can be moved with TransferState directly: transferState.set(key, value) on the server, transferState.get(key, default) on the client.

The <script> island inserted into the HTML is the transport.

SSR-specific lifecycle hooks

  • afterNextRender(fn, {phases: ['read' | 'mixedReadWrite' | 'write']}) — schedule work for after the first client render. On the server, these hooks don't fire — they're explicitly client-only. Use them for DOM-touching code.
  • The isPlatformServer(injector.get(PLATFORM_ID)) and isPlatformBrowser(...) predicates from @angular/common are still the official platform check.

Provider configuration

Server-side:

const html = await renderApplication(AppRoot, {
  url: '/some-route',
  document: '<!doctype html><html>…</html>',
  providers: [
    provideServerRendering(),
    provideRouter(routes),
    provideHttpClient(withFetch()),
  ],
});

Client-side:

bootstrapApplication(AppRoot, {
  providers: [
    provideClientHydration(),
    provideRouter(routes),
    provideHttpClient(withFetch()),
  ],
});

The mirroring is intentional: every server-side provider has a matching client-side provider.

Build-system integration

The CLI's @angular/build package ships the build adapter:

  • Generates separate browser and server bundles.
  • Includes a server.ts entry that wires the renderer behind an Express handler by default.
  • For SSG / prerendering, runs the rendering loop at build time with a list of routes.

The @angular/ssr package wraps the renderer in opinionated AngularNodeAppEngine / AngularAppEngine adapters used by the generated server.ts.

Performance notes

  • Domino is not zero-cost. Server renders are CPU-bound. Cache rendered HTML aggressively where possible.
  • Avoid blocking work in ngOnInit when SSR is in play. The application waits for stability; an HTTP request that takes 5s blocks the response by 5s.
  • Use provideHttpClient(withFetch()) for SSR — it integrates with response streaming and caching better than the XHR backend.

Integration points

  • @angular/platform-server — the renderer.
  • @angular/core hydration — the client-side counterpart.
  • @angular/router — receives the URL via INITIAL_CONFIG.
  • @angular/common/http — uses TransferState to deduplicate requests across server/client boundary.
  • @angular/service-worker — turns off during SSR; activates only on the client.
  • Build pipeline@angular/build (CLI repo).

Where to start when modifying

The integration test at integration/platform-server-hydration/ is the canonical reproduction harness.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Server-side rendering – Angular wiki | Factory