Angular SSR Hydration and Event Replay

This guide is part of Angular Performance, within Framework Performance. A client-rendered Angular app shows nothing meaningful until its JavaScript has downloaded, parsed and bootstrapped, and then fetched data — a long wait on mobile, and poor LCP. Server-side rendering sends complete HTML so content appears immediately. Older Angular SSR used destructive hydration: on bootstrap, the client discarded the server DOM and re-rendered it, causing flicker, layout shifts and wasted work.

Modern Angular uses non-destructive hydration: the client reuses the server-rendered DOM, attaching listeners and state without recreating nodes. Event replay captures user events (clicks, inputs) that happen before hydration completes and replays them once the app is ready, so early interactions are not lost. Incremental hydration extends this with @defer blocks that hydrate on triggers. Together, they give fast LCP from SSR without the classic hydration problems.

Event replay during hydration Sequence showing a click captured before hydration and replayed after the app hydrates. Event replay during hydration User Server HTML Event replay Angular app content visible (SSR) click before hydration hydrate (reuse DOM) replay click

Rapid Diagnosis

  • Check whether SSR is enabled (@angular/ssr) and whether provideClientHydration is configured.
  • Look for hydration warnings in the browser console (NG05xx errors about mismatches).
  • Check LCP on client-rendered routes vs server-rendered ones.
  • Test early clicks: tap buttons immediately after the page appears and see whether they respond.

Root Cause Analysis

1. Client-side rendering. Content waits for JavaScript and data.

2. Destructive or missing hydration. DOM recreated, causing flicker and CLS.

3. Hydration mismatches. Direct DOM manipulation or browser-only code produces different DOM on server and client.

4. Lost early interactions. Clicks before hydration are ignored without event replay.

Step-by-Step Resolution

1. Add SSR and hydration

bash
ng add @angular/ssr
typescript
// app.config.ts
import { provideClientHydration, withEventReplay, withIncrementalHydration } from '@angular/platform-browser';
export const appConfig = {
  providers: [provideClientHydration(withEventReplay(), withIncrementalHydration())],
};
// trade-off: SSR adds server cost and complexity; cache rendered pages or prerender
// static routes so the server does not render every request.

2. Fix browser-only code

Move code that uses window, document, localStorage or measures layout into afterNextRender or afterRender, which run only in the browser after rendering.

3. Avoid direct DOM manipulation

Replace ElementRef.nativeElement manipulation and third-party DOM libraries during render with Angular bindings, or mark components with ngSkipHydration as a last resort.

4. Transfer data to avoid refetching

HttpClient responses made during SSR are cached and transferred to the client by default with hydration, so the client does not repeat requests.

LCP p75 on a landing page by rendering approach Bar chart comparing LCP for client-side rendering, SSR with destructive hydration and SSR with non-destructive hydration. LCP p75 on a landing page by rendering approach Client-side rendering 3900ms SSR + destructive hydration 2300ms SSR + non-destructive hydration 1900ms LCP good

Verification

Load the page and check the console for hydration messages (Angular logs hydration statistics in development). The DOM should not flicker after bootstrap. Click buttons immediately after content appears; with event replay, actions should complete after hydration. In the field, LCP should improve for SSR routes, and CLS should not increase.

Worked Example: A Travel Booking Landing Page

A travel company's Angular landing pages were client-rendered: LCP p75 on mobile was 3.9 seconds. Enabling SSR with destructive hydration (an older setup) improved LCP to 2.3 seconds but caused a visible flash as the app re-rendered, and CLS rose to 0.12. Upgrading to non-destructive hydration with event replay removed the flash (CLS back to 0.02) and improved LCP to 1.9 seconds because the browser no longer repainted the hero. Search form taps during the first seconds — previously ignored — were replayed, and form start rate rose 6%. Two components using a jQuery date picker needed ngSkipHydration until replaced.

Hydration Mismatches and How to Find Them

A mismatch occurs when the client expects different DOM than the server produced: different text (dates formatted with different time zones), elements added only on the client, invalid HTML that the browser parser corrects (a div inside a p), or third-party scripts that modify the DOM before hydration. Angular reports mismatches with error codes and the affected component in development. Fix the cause: render consistent data on both sides (pass server-computed values via transfer state), keep HTML valid, and defer browser-only rendering to afterNextRender. ngSkipHydration makes a component re-render on the client instead of hydrating; use it sparingly.

Common hydration mismatch causes Typical causes of Angular hydration mismatches and how to fix them. Common hydration mismatch causes Cause Symptom Fix Dates or random values text differs compute once on server and transfer Browser-only APIs in templates server errors or missing nodes afterNextRender Invalid HTML nesting DOM restructured by parser fix markup Third-party DOM libraries unexpected nodes replace or ngSkipHydration

Caching SSR Output

SSR moves rendering cost to the server. For routes whose content is the same for many users, prerender them at build time (Angular supports route prerendering) or cache rendered HTML at the CDN. Personalised routes can render a shared shell on the server and load personal data on the client after hydration. Without caching, SSR improves LCP but can make TTFB the new bottleneck under load.

Rolling Out SSR Safely

Introduce SSR route by route. Start with public, mostly static pages (landing, marketing, content), where the LCP gain is largest and personalisation is minimal. Run the app with hydration in development and fix every mismatch warning before enabling it in production. Monitor server error rates and TTFB after launch, since server rendering introduces new failure modes (missing browser APIs, timeouts in data fetching). Then extend SSR to more routes, adding prerendering or caching where content allows.

Common Mistakes

  • Using window or document during render. Errors on the server or mismatches.
  • Overusing ngSkipHydration. Re-rendering defeats hydration's benefits.
  • No event replay. Early clicks are lost.
  • Uncached SSR for static pages. Server cost and slower TTFB.

Edge Cases

i18n. Locale-specific rendering must match between server and client; use the same locale data.

Authentication. Server rendering with user-specific data must avoid caching personal pages.

Zoneless. Hydration works without zone.js; mark pending async tasks so SSR waits for data.

Streaming. Angular SSR renders full pages; combine with caching and fast data sources for good TTFB.

FAQ

What is non-destructive hydration?

Hydration that reuses the server-rendered DOM, attaching Angular to existing nodes instead of re-creating them.

What does event replay do?

It records user events that occur before hydration finishes and replays them afterwards, so early interactions are not lost.

How do I fix hydration errors?

Read the error code and component in the console, then make server and client render the same DOM: consistent data, valid HTML, browser-only code in afterNextRender.

Does SSR improve INP?

Indirectly at best. SSR mainly improves LCP. INP depends on hydration cost and interaction handling; incremental hydration and zoneless help there.

What is ngSkipHydration?

An attribute that tells Angular to re-render a component on the client instead of hydrating it, as a workaround for components that cannot hydrate.

Should every route be server-rendered?

Public routes benefit most. Private, highly interactive routes may be fine client-rendered, and static routes should be prerendered.

Does HttpClient refetch data after SSR?

Not by default with hydration: responses are transferred from the server and reused on the client.

Can I prerender Angular routes?

Yes. Angular supports prerendering routes at build time, which gives static HTML with the same hydration behaviour.

Does event replay work for all events?

It covers common user events such as clicks and inputs. Check the documentation for the event types supported in your Angular version.

How do I know hydration succeeded?

In development, Angular logs hydration statistics (components hydrated, skipped) to the console; mismatches appear as errors with codes.

Is SSR required for good Angular performance?

Not for private app screens, but for public pages it is the most reliable way to get a good LCP.