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:
Server Rendering : Angular renders the application on the server and adds special hydration markers (comments) to the HTML
Client Bootstrap : Angular bootstraps on the client and locates hydration markers
DOM Reuse : Angular attaches to existing DOM nodes instead of creating new ones
Event Replay : User interactions during hydration are captured and replayed
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 );
}
}
}
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:
Check for random values or timestamps
Verify async operations complete before rendering
Ensure consistent data between server and client
Look for browser-only code running during SSR
If hydration is slow:
Enable incremental hydration for large apps
Lazy load non-critical components
Optimize server rendering time
Reduce bundle size
Use event replay for better perceived performance
Additional Resources