Skip to main content

Overview

Hydration is the process of attaching Angular’s client-side JavaScript to server-rendered HTML, making the application interactive without re-rendering the entire page. This provides the best of both worlds: fast initial page load from SSR and full interactivity from the client-side application.

Why Hydration?

Without hydration, Angular would destroy the server-rendered DOM and re-render everything on the client, causing:
  • Visible content flash
  • Lost user interactions during transition
  • Wasted rendering work
  • Poor user experience

No Content Flash

Server-rendered content stays visible during client bootstrap.

Faster Interactivity

Application becomes interactive faster by reusing existing DOM.

Better Performance

Avoids duplicate rendering work on the client.

Improved Core Web Vitals

Better LCP (Largest Contentful Paint) and CLS (Cumulative Layout Shift) scores.

Enabling Hydration

Client Configuration

Add provideClientHydration() to your application configuration.
// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideClientHydration } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration()
  ]
}).catch(err => console.error(err));

Server Configuration

No additional configuration needed on the server. Hydration metadata is automatically added during SSR.
// app.config.server.ts
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';

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

export const config = mergeApplicationConfig(appConfig, serverConfig);

How Hydration Works

Angular’s hydration process involves several steps:
  1. Server Rendering: Angular renders the application on the server and adds special hydration markers (comments) to the HTML
  2. Client Bootstrap: Angular bootstraps on the client and locates hydration markers
  3. DOM Reuse: Angular attaches to existing DOM nodes instead of creating new ones
  4. Event Replay: User interactions during hydration are captured and replayed
  5. Full Interactivity: Application becomes fully interactive
<!-- Server-rendered HTML with hydration markers -->
<app-root>
  <!--ngh="0"-->
  <div>
    <h1>Welcome</h1>
    <!--ngh="1"-->
    <p>Server-rendered content</p>
    <!--/ngh-->
  </div>
  <!--/ngh-->
</app-root>

Advanced Features

I18n Support

Enable hydration support for internationalized applications.
import { bootstrapApplication } from '@angular/platform-browser';
import { 
  provideClientHydration, 
  withI18nSupport 
} from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withI18nSupport()
    )
  ]
});

Event Replay

Capture and replay user interactions that occur during hydration.
import { bootstrapApplication } from '@angular/platform-browser';
import { 
  provideClientHydration, 
  withEventReplay 
} from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withEventReplay()
    )
  ]
});
Event replay ensures that user clicks and interactions during the hydration process are not lost and are executed once the application is fully interactive.

Incremental Hydration

Hydrate components on-demand for better performance with large applications.
import { bootstrapApplication } from '@angular/platform-browser';
import { 
  provideClientHydration, 
  withIncrementalHydration 
} from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withIncrementalHydration()
    )
  ]
});

Hydration-Compatible Code

Direct DOM Manipulation

Avoid direct DOM manipulation that conflicts with hydration.
// ❌ Bad: Direct DOM manipulation
import { Component, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-bad',
  template: `<div #container></div>`
})
export class BadComponent implements AfterViewInit {
  constructor(private elementRef: ElementRef) {}

  ngAfterViewInit(): void {
    // This conflicts with hydration
    this.elementRef.nativeElement.innerHTML = '<p>Dynamic content</p>';
  }
}

// ✅ Good: Use Angular templating
import { Component, signal } from '@angular/core';

@Component({
  selector: 'app-good',
  template: `
    <div>
      <p *ngIf="showContent()">Dynamic content</p>
    </div>
  `
})
export class GoodComponent {
  showContent = signal(false);

  ngAfterViewInit(): void {
    this.showContent.set(true);
  }
}

Skip Hydration

Skip hydration for specific components when necessary.
import { Component } from '@angular/core';
import { NgComponentOutlet } from '@angular/common';

@Component({
  selector: 'app-dynamic',
  template: `
    <!-- Skip hydration for this component -->
    <div ngSkipHydration>
      <third-party-component></third-party-component>
    </div>
  `
})
export class DynamicComponent {}

Handle Browser-Only Features

Ensure code works correctly during both SSR and hydration.
import { 
  Component, 
  OnInit, 
  PLATFORM_ID, 
  Inject,
  signal 
} from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-browser-feature',
  template: `
    <div>
      <p>Window width: {{ windowWidth() }}</p>
      <p>Is touch device: {{ isTouchDevice() }}</p>
    </div>
  `
})
export class BrowserFeatureComponent implements OnInit {
  windowWidth = signal(0);
  isTouchDevice = signal(false);

  constructor(@Inject(PLATFORM_ID) private platformId: object) {}

  ngOnInit(): void {
    if (isPlatformBrowser(this.platformId)) {
      // Safe to access browser APIs
      this.windowWidth.set(window.innerWidth);
      this.isTouchDevice.set('ontouchstart' in window);

      // Set up resize listener
      window.addEventListener('resize', () => {
        this.windowWidth.set(window.innerWidth);
      });
    }
  }
}

Debugging Hydration

Enable Debug Mode

Enable hydration debugging to identify issues.
import { bootstrapApplication } from '@angular/platform-browser';
import { 
  provideClientHydration,
  withDebugTracing
} from '@angular/platform-browser';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideClientHydration(
      withDebugTracing()
    )
  ]
});
This will log hydration information to the console:
Angular hydration: Found 42 nodes to hydrate
Angular hydration: Successfully hydrated component AppComponent
Angular hydration: Hydration completed in 125ms

Common Hydration Errors

Hydration Mismatch: Server and client rendered different content.
// ❌ Causes hydration mismatch
import { Component } from '@angular/core';

@Component({
  selector: 'app-time',
  template: `<p>Current time: {{ currentTime }}</p>`
})
export class TimeComponent {
  // Different value on server vs client!
  currentTime = new Date().toISOString();
}

// ✅ Fixed: Consistent rendering
import { Component, OnInit, signal } from '@angular/core';

@Component({
  selector: 'app-time',
  template: `<p>Current time: {{ currentTime() }}</p>`
})
export class TimeComponent implements OnInit {
  currentTime = signal('Loading...');

  ngOnInit(): void {
    // Update after hydration
    if (typeof window !== 'undefined') {
      setTimeout(() => {
        this.currentTime.set(new Date().toISOString());
      }, 0);
    }
  }
}

Performance Monitoring

Monitor hydration performance in your application.
import { 
  Component, 
  OnInit, 
  ApplicationRef,
  inject 
} from '@angular/core';

@Component({
  selector: 'app-root',
  template: `<router-outlet></router-outlet>`
})
export class AppComponent implements OnInit {
  private appRef = inject(ApplicationRef);

  ngOnInit(): void {
    // Monitor when app becomes stable (hydration complete)
    this.appRef.isStable.subscribe(stable => {
      if (stable) {
        const hydrationTime = performance.now();
        console.log(`Hydration completed in ${hydrationTime}ms`);
        
        // Report to analytics
        this.reportHydrationMetrics(hydrationTime);
      }
    });
  }

  private reportHydrationMetrics(time: number): void {
    // Send to your analytics service
    if (typeof window !== 'undefined' && 'gtag' in window) {
      (window as any).gtag('event', 'hydration_complete', {
        value: Math.round(time),
        event_category: 'performance'
      });
    }
  }
}

Testing with Hydration

import { TestBed } from '@angular/core/testing';
import { provideClientHydration } from '@angular/platform-browser';
import { MyComponent } from './my.component';

describe('MyComponent with Hydration', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [MyComponent],
      providers: [
        provideClientHydration()
      ]
    }).compileComponents();
  });

  it('should hydrate correctly', async () => {
    const fixture = TestBed.createComponent(MyComponent);
    
    // Wait for hydration to complete
    await fixture.whenStable();
    
    const element = fixture.nativeElement;
    expect(element.textContent).toContain('Expected content');
  });
});

Best Practices

Avoid Direct DOM Access

Use Angular’s templating and rendering APIs instead of direct DOM manipulation.

Consistent Rendering

Ensure server and client render identical content for successful hydration.

Use Platform Checks

Check platform before accessing browser-only APIs.

Monitor Performance

Track hydration metrics to identify and fix performance issues.

Hydration Checklist

Enable provideClientHydration() in your application
Avoid random values or timestamps in templates
Use platform checks for browser APIs
Test hydration in development and production modes
Monitor hydration performance metrics
Handle third-party libraries with ngSkipHydration

Troubleshooting

Content Mismatch

If you see hydration mismatch warnings:
  1. Check for random values or timestamps
  2. Verify async operations complete before rendering
  3. Ensure consistent data between server and client
  4. Look for browser-only code running during SSR

Performance Issues

If hydration is slow:
  1. Enable incremental hydration for large apps
  2. Lazy load non-critical components
  3. Optimize server rendering time
  4. Reduce bundle size
  5. Use event replay for better perceived performance

Additional Resources