Incremental hydration is an advanced type of hydration you can leave sections of your app dehydrated and hydration of choice sections as needed.
Why use incremental hydration?
Incremental hydration is an optimization technique that enhances the concept of full application hydration, making it more efficient.
This approach allows for smaller initial bundles while still delivering a user experience nearly equivalent to that of full application hydration.
Smaller bundles enhance initial load times by reducing First Input Delay (FID) and Cumulative Layout Shift (CLS).
Incremental hydration also enables the use of deferrable views (@defer) for content that previously couldn’t be deferred.
Specifically, you can now use deferrable views for content that is above the fold.
Before incremental hydration, placing a @defer block above the fold caused placeholder content to render first and then be replaced by the main template content of the @defer block.
This caused a layout shift. Incremental hydration ensures that the main template of the @defer block renders without any layout shift during hydration.
How do you enable incremental hydration in Angular?
Incremental hydration can be activated in applications that already utilize server-side rendering (SSR) with hydration.
First, follow the Angular SSR Guide to enable server-side rendering, and then follow the Angular Hydration Guide to enable hydration.
Enable incremental hydration by adding the withIncrementalHydration() function to the provideClientHydration provider in the app.config.ts file.
import {ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection} from '@angular/core';
import {provideRouter} from '@angular/router';
import {routes} from './app.routes';
import {provideClientHydration, withEventReplay, withIncrementalHydration} from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes), provideClientHydration(withEventReplay()),
provideClientHydration(withIncrementalHydration()),
]
};
Incremental hydration automatically relies on and enables event replay.
If withEventReplay() is already in your list, you can safely remove it after enabling incremental hydration.
How does incremental Hydration work?
Incremental hydration builds upon full-application hydration, deferrable views, and event replay.
Incremental hydration allows you to specify extra triggers for @defer blocks that mark the boundaries of its application.
By adding a hydrate trigger to a @defer block, Angular loads its dependencies during server-side rendering and displays the main template rather than the @placeholder.
During client-side rendering, the dependencies remain deferred, and the content of the @defer block stays dehydrated until its hydrate trigger is activated.
This trigger instructs the @defer block to fetch its dependencies and hydrate the content. Any browser events—particularly those matching listeners registered in your component—that occur before hydration are queued and replayed once hydration is complete.
Controlling hydration of content with triggers.
These are extra triggers that can be used in combination with the standard @defer triggers.
Each @defer block may have multiple hydrate event triggers, separated with a semicolon (;).
Angular initiates hydration whenever any of the triggers are activated.
There are three types of hydrate triggers: hydrate on, hydrate when, and hydrate never.
Hydrate on:
The hydrate on parameter specifies the condition under which hydration is triggered for the @defer block.
The available triggers are as follows:
Trigger | Description |
hydrate on idle | Triggers when the browser is idle. |
hydrate on viewport | Triggers when specified content enters the viewport. |
hydrate on interaction | Triggers when the user interacts with specified element. |
hydrate on hover | Triggers when the mouse hovers over specified area. |
hydrate on immediate | Triggers immediately after non-deferred content has finished rendering. |
hydrate on timer | Triggers after a specific duration. |
Hydration on idle.
The hydrate on idle trigger loads the deferrable view’s dependencies and hydrates the content once the browser enters an idle state, using the requestIdleCallback mechanism.
I will use examples from my own projects because using examples from made-up situations doesn’t work for me.
For example, I used it in an online store, when it was necessary to wait until the main content finished loading.
After that, the product cards, filters, recommendations, and reviews became active.
<app-first/>
@defer (hydrate on idle) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
hydrate on viewport.
The hydrate on viewport trigger starts loading the deferrable view’s dependencies and hydrates the relevant section of the app once the specified element becomes visible in the viewport using the Intersection Observer API.
Hydration on viewport is also good for the bottom part of the page. I didn’t use it for an online store, but I used it for an educational platform.
I used this method to activate learning materials and cards.
<app-first/>
@defer (hydrate on viewport) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
hydrate on interaction.
The hydrate on interaction trigger fetches the deferrable view’s dependencies and hydrates the content when the user engages with the specified element using either click or keydown actions.
It is perfect for activating elements on interaction. It is also good for use in online stores and educational platforms, but I haven’t used it in my work yet.
<app-first/>
@defer (hydrate on interaction) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
hydrate on hover.
The hydrate on hover trigger loads the deferrable view's dependencies and hydrates the content when the mouse has hovered over the triggered area though the mouseover and focusing events.
When hovering over a product, a component with detailed information or buttons like “Quick View / Add to Cart” loads.
When hovering over a lesson card, a block with additional materials loads.
Like the previous case and the following ones, I haven’t used this myself yet.
<app-first/>
@defer (hydrate on hover) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
hydrate on immediate.
The hydrate on immediate trigger loads the deferrable view's dependencies and hydrates the content immediately.
This means the deferred block loads immediately after all other non-deferred content has finished rendering.
<app-first/>
@defer (hydrate on immediate) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
hydrate on timer.
The hydrate on timer trigger loads the deferrable view’s dependencies and hydrates the content after a set amount of time.
<app-first/>
@defer (hydrate on timer(1000)) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
The duration parameter must be specified in milliseconds (ms) or seconds (s).
hydrate when.
The hydrate when trigger takes a custom condition and initiates loading of the deferrable view’s dependencies and hydration of the content once the condition evaluates to true.
<app-first/>
@defer (hydrate when condition) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
NOTE: hydrate when conditions only trigger when they are the top-most dehydrated @defer block.
The condition provided for the trigger is specified in the parent component, which needs to exist before it can be triggered.
If the parent block is dehydrated, that expression will not yet be resolvable by Angular.
hydrate never.
The hydrate never trigger lets developers specify that the content inside the @defer block should stay dehydrated permanently, effectively making it static content.
Note that this applies to the initial render only.
During a later client-side render, a @defer block with the hydrate never trigger would still fetch its dependencies, since hydration only applies to the initial load of server-rendered content.
In the example below, during subsequent client-side renders, the @defer block dependencies would load when the element enters the viewport.
<app-first/>
@defer (on viewport; hydrate never) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
NOTE: Using hydrate never prevents hydration of the entire nested subtree of a given @defer block. No other hydrate triggers fire for content nested underneath that block.
Hydrate triggers alongside regular triggers.
Hydrate triggers are additional triggers that are used alongside regular triggers on a @defer block.
Hydration is an initial load optimization, and that means hydrate triggers only apply to that initial load.
Any subsequent client side render will still use the regular trigger.
<app-first/>
@defer (on idle; hydrate on interaction) {
<app-second/>
} @placeholder {
<div>Second placeholder</div>
}
In this example, during the initial load, the hydrate on interaction trigger applies. Hydration will occur when the user interacts with the
<app-second /> component. On any subsequent client-side page loads, such as when navigating via a routerLink to a page containing this component, the on idle trigger will apply.
How does incremental hydration work with nested @defer blocks?
Angular's component and dependency system is hierarchical, which means hydrating any component requires all of its parents also be hydrated.
So if hydration is triggered for a child @defer block of a nested set of dehydrated @defer blocks, hydration is triggered from the top-most dehydrated @defer block down to the triggered child and fire in that order.
@defer (hydrate on interaction) {
<app-first/>
@defer (hydrate on hover) {
<app-second/>
} @placeholder {
<div>First placeholder</div>
}
} @placeholder {
<div>Second Placeholder</div>
}
In the above example, hovering over the nested @defer block triggers hydration. The parent @defer block with the <app-first /> hydrates first, then the child @defer block with <app-second /> hydrates after.
Do I still need to specify @placeholder blocks?
At the top, when I was describing other hydration methods, I didn’t include @placeholder blocks because it never made much sense — this block always stays in the same place. So what exactly is this @placeholder block?
As for whether it is necessary to use the @placeholder block.
Yes. The content of a @placeholder block isn’t used during incremental hydration, but it’s still required for subsequent client-side rendering cases.
If your content wasn’t on the route that was part of the initial load, then when navigating to a route that contains a @defer block, its content is rendered as a regular @defer block. Therefore, in such client-side rendering cases, the @placeholder is displayed.