Understanding Hydration in Angular
Hydration refers to the process of re-establishing a server-rendered application on the client side. This involves reusing the DOM elements created by the server, preserving application state, transferring data already fetched by the server, and performing other related tasks.
If you have used the Angular CLI to enable server-side rendering (SSR), whether during initial project setup or later by executing ng add @angular/ssr, the necessary code for enabling hydration will already be present in your application.
ng add @angular/ssr
Here is an example of how your app.config file may appear:
import {
bootstrapApplication,
provideClientHydration,
} from '@angular/platform-browser';
...
bootstrapApplication(AppComponent, {
providers: [provideClientHydration()]
});
Verifying That Hydration Is Active
To confirm that hydration is enabled in a development environment, open the browser's Developer Tools and inspect the console. You should encounter a log message that includes hydration statistics, such as the total number of hydrated components and nodes. Angular calculates these figures based on every component rendered on the page, including those originating from third-party libraries.
Enabling User Interaction Before Hydration Completes
When an application is rendered on the server, the generated HTML becomes visible in the browser as soon as it arrives. Users may assume they can interact with the page immediately, but event listeners are not attached until the hydration process finishes. Starting with version 18, the Event Replay feature can be enabled. This feature captures all events occurring before hydration and replays them once hydration is complete. It can be activated using the withEventReplay() function. For instance:
import {provideClientHydration, withEventReplay} from '@angular/platform-browser';
...
bootstrapApplication(App, {
providers: [
provideClientHydration(withEventReplay())
]
});
Excluding Specific Components from Hydration
Some components may not operate correctly when hydration is enabled, often due to issues such as direct DOM manipulation. A workaround is to add the ngSkipHydration attribute to a component's tag, which skips the hydration process for that entire component.
<app-example ngSkipHydration />
Alternatively:
@Component({
...
host: {ngSkipHydration: 'true'},
})
class UserProjectsComponent {}
Configuration for Incremental Hydration
A configuration object, appConfig, defines settings for an Angular application using incremental hydration. The ApplicationConfig object contains a providers array, which registers services or capabilities that are accessible throughout the application.
In this scenario, client-side hydration is enabled via provideClientHydration, combined with the withPartialHydration() option. A detailed explanation follows:
provideClientHydration(): This function enables client-side hydration in the application. Hydration is the process of turning static content (typically generated during server-side rendering) into fully interactive, dynamic content once the client-side JavaScript has loaded.
withPartialHydration(): This option enables partial hydration, which allows specific sections of the application to be hydrated incrementally rather than hydrating the entire page all at once. It improves performance by hydrating only the necessary parts of the app based on user interaction or predefined triggers.
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(withPartialHydration())
]
ο½οΌ
Incremental Hydration in Practice
Deferrable views, also known as defer blocks, serve as the foundational unit and boundary for incremental hydration in Angular.
A defer block establishes an incremental hydration boundary by applying conditions such as hydrate on, hydrate when, or hydrate never. It supports triggers like idle, viewport, and interaction.
For example:
<user-detail />
@defer (on immediate; hydrate on interaction) {
<deferred-user-projects />
<deferred-user-blogs />
}
Nested Hydration
When multiple @defer blocks are in a dehydrated state, their conditions are evaluated concurrently. For instance, consider the following structure:
@defer (hydrate on hover) {
@defer (hydrate on timer(15s)) {
...
}
}
The outer block configured with "hydrate on hover" will initiate hydration if the mouse hovers over any portion of its content. Even if that block is never hovered, the inner block's "hydrate on timer(15s)" setting will trigger hydration after a 15-second delay.
The never trigger for hydration:
Angular's @defer (hydrate never) trigger establishes a defer block that is permanently excluded from hydration. As a result, the markup inside this block stays inert and fully static.
@defer (hydrate never) {
<user-projects />
}
- The
<example-component>is rendered on the server but never hydrated on the client. - Even after the full application has loaded in the browser, the contents of this defer block remain non-interactive and unchanged.
This approach suits situations where certain UI sections don't need to respond to user actions or where keeping them passive helps conserve client-side resources.
References:
Angular RFC: https://github.com/angular/angular/discussions/57664
Angular Source PR: https://github.com/angular/angular/pull/58193
