SSR Hydration in Angular 19: A Complete Performance Guide

Sayeed Mohammad9 min read

Angular 19s improved hydration changes how your app loads. Heres a practical guide to setting it up correctly and avoiding the common pitfalls.

What is SSR Hydration?

Server-Side Rendering (SSR) in Angular means your app renders HTML on the server before sending it to the browser. Hydration is the process of Angular "taking over" that pre-rendered HTML on the client — attaching event listeners and making it interactive — without throwing away and re-rendering the DOM.

Without hydration, Angular would destroy the server-rendered HTML and rebuild the entire DOM on the client. That flicker is what hydration eliminates.

Setting Up SSR in Angular 19

Angular 19 ships with SSR and hydration support built in. Add it to a new project:

bash
ng new my-app --ssr

Or add to an existing project:

bash
ng add @angular/ssr

This creates app.config.server.ts and updates your build config automatically.

Enabling Hydration

In your app.config.ts:

typescript
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideClientHydration(withEventReplay()), // ← key line
    provideHttpClient(withFetch()),
  ],
};

withEventReplay() captures user interactions (clicks, inputs) that happen before hydration completes and replays them afterwards. This eliminates the "dead click" problem.

The Hydration Gotchas

1. DOM Mismatch Errors

The most common issue. If your server-rendered HTML doesn't exactly match what the client would render, Angular throws a hydration mismatch warning:

NG0500: Angular hydration expected X nodes but found Y.

Common causes:

typescript
// ❌ This causes mismatches — Date is different on server vs client
@Component({
  template: `<p>Loaded at {{ new Date().toISOString() }}</p>`
})

// ✅ Use signals with afterNextRender for client-only values
@Component({
  template: `<p>{{ loadedAt() }}</p>`
})
export class MyComponent {
  loadedAt = signal('');

  constructor() {
    afterNextRender(() => {
      this.loadedAt.set(new Date().toISOString());
    });
  }
}

2. Skip Hydration for Dynamic Content

If a component's output is inherently client-only (canvas, maps, third-party widgets), skip hydration for it:

typescript
@Component({
  selector: 'app-map',
  template: `<div id="map"></div>`,
  host: { ngSkipHydration: 'true' } // ← skip this component
})
export class MapComponent { }

Measuring the Impact

After enabling SSR + hydration on a real e-commerce Angular app, here's what we measured:

Metric Before After
LCP 4.2s 1.8s
FCP 3.8s 1.1s
PageSpeed Score 42 91
TTI 6.1s 2.9s

The Largest Contentful Paint improvement is the most significant — users see real content in under 2 seconds instead of waiting for the JS bundle to execute.

Transfer State: Avoiding Double HTTP Requests

Without Transfer State, your app makes HTTP requests on the server, then makes the same requests again on the client. Fix this with withHttpTransferCache:

typescript
provideClientHydration(
  withEventReplay(),
  withHttpTransferCache() // ← cache server HTTP responses for client
)

Now API responses made during SSR are transferred to the client in the initial HTML payload — no duplicate requests.

Conclusion

Angular 19's SSR hydration is production-ready and the performance gains are substantial. The setup is straightforward — the main work is identifying and fixing DOM mismatch issues, which are usually caused by browser-only APIs (Date, window, localStorage) running on the server.

Enable it, measure your Core Web Vitals before and after, and let the numbers justify the effort.

Comments

Leave a comment