Incremental hydration in Angular lets you defer loading and bootstrapping of JavaScript for non-critical parts of a page until they are needed, improving initial load performance. You configure it by marking components as @defer blocks or using the @if and @for syntax with hydration triggers, and then enabling hydration in your app config. This article explains the mechanics, how to set triggers, and common pitfalls.
What Is Hydration and Why Incremental?
In a traditional Angular app, the server sends fully rendered HTML (via Angular Universal or Angular SSR), but the client still needs to download the entire JavaScript bundle and run it to attach event listeners and make the page interactive. That process is called hydration. Without it, the page looks ready but buttons don’t click, forms don’t submit, and dynamic content doesn’t update.
Incremental hydration breaks this into smaller pieces. Instead of hydrating the whole page at once, Angular hydrates only the parts that are currently visible or that the user is likely to interact with soon. The rest stays as static HTML until a trigger fires. This reduces the amount of JavaScript that must be downloaded and executed on initial load, which is especially valuable on slow connections or low-end devices.
How Incremental Hydration Works in Angular
Angular implements incremental hydration through deferrable views, introduced in version 17 and refined in version 18. You wrap a section of your template in an @defer block. The block’s content is not rendered (or hydrated) until a specified condition becomes true. The condition is defined by a trigger, such as the element being scrolled into view, a timer expiring, or an interaction event.
Here’s a minimal example:
@defer (on viewport) {
<app-heavy-chart />
} @placeholder {
<p>Chart will load when visible.</p>
}
When the browser detects that the placeholder enters the viewport, Angular fetches the code for app-heavy-chart (if it’s lazy-loaded) and hydrates it. Until then, only the placeholder is shown. The @placeholder block is optional but recommended to avoid layout shift.
Under the hood, Angular uses its dependency injection system to manage the deferred blocks. Each block is a separate “injector� that can be destroyed and recreated. When the trigger fires, Angular creates the component and attaches event listeners. If the trigger never fires (e.g., the user never scrolls to that section), the component never hydrates, saving resources.
Configuring Triggers for Incremental Hydration
Triggers are the conditions that cause a deferred block to hydrate. Angular provides several built-in triggers, and you can combine them with logical operators.
on viewport– fires when the placeholder enters the browser viewport (using IntersectionObserver).on interaction– fires when the user clicks, touches, or focuses the placeholder or a specified element.on hover– fires on mouse hover or touchstart.on timer(<time>)– fires after a specified delay (e.g.,on timer(5000)for 5 seconds).on immediate– fires as soon as the application has finished initial rendering.on idle– fires when the browser is idle (usingrequestIdleCallback).
You can also use when with a boolean expression. For example:
@defer (when userLoggedIn) {
<app-user-profile />
}
This hydrates the block when userLoggedIn becomes true. You can combine triggers with a pipe (|) for OR logic, or with a comma for AND logic:
@defer (on viewport, on timer(2000)) {
<app-ads />
}
The above hydrates only when both the viewport condition and the timer are satisfied. To use OR, use |:
@defer (on viewport | on interaction) {
<app-chart />
}
How to Enable Hydration in Your Angular App
Incremental hydration requires that you have server-side rendering (SSR) set up, because the whole point is to send HTML that can be hydrated. If you haven’t enabled SSR, you can add it with:
ng add @angular/ssr
Once SSR is active, you must explicitly enable hydration in your app.config.ts:
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [provideClientHydration()]
};
Without this provider, Angular will not hydrate any server-rendered content, and your @defer blocks will simply render as empty placeholders on the client. After enabling hydration, you can start using @defer blocks in your templates. The browser will automatically hydrate the blocks that are visible on the initial load, and defer the rest.
Choosing the Right Trigger for Your Use Case
The choice of trigger depends on what the deferred component does and how likely the user is to interact with it.
- For below-the-fold content that the user will likely scroll to (e.g., a long article’s comments section), use
on viewport. - For interactive widgets like a chat box or a login form, use
on interactionso that it loads only when the user clicks it. - For non-essential elements that can appear after a delay (e.g., a newsletter popup), use
on timer(3000). - For heavy components that are needed immediately but should not block the initial render, use
on idle. - For content that depends on application state (e.g., a user’s cart), use
whenwith a boolean.
You can also nest @defer blocks. For example, a parent block might load on viewport, and inside it, a child block loads on interaction. This gives you fine-grained control over what gets hydrated and when.
Common Pitfalls and How to Avoid Them
Incremental hydration is powerful, but it introduces new challenges that can break your app if not handled carefully.
- Layout shift: If the placeholder has a different size than the deferred content, the page jumps when the content loads. Always provide a placeholder with the same dimensions, or use CSS to reserve space.
- Missing event listeners: If you attach event listeners in
ngOnInitof a deferred component, they won’t be attached until hydration. That’s expected, but make sure your UI doesn’t rely on those events before the component is hydrated. - Zones and change detection: Deferred blocks are outside the main change detection tree until they are hydrated. If you have a service that updates state globally, the deferred component might not reflect those changes until it hydrates. Use
ChangeDetectorRefor signals to handle this. - SSR and
@defer: On the server,@deferblocks are not rendered by default. That means the placeholder is sent to the client. If you need the content to be present in the initial HTML for SEO, you should not defer it.
Incremental Hydration vs. Lazy Loading
Lazy loading and incremental hydration are often confused, but they solve different problems. Lazy loading (via loadComponent or loadChildren) defers the download of a component’s JavaScript until the route is visited. Incremental hydration defers the hydration of already-rendered HTML. You can use both together: a lazy-loaded component can be wrapped in an @defer block, so that its JavaScript is fetched only when the trigger fires, and then it hydrates immediately.
In practice, you’ll often use @defer with lazy-loaded components to maximize performance. For example, a heavy chart component can be lazy-loaded and deferred until it scrolls into view. This way, the initial bundle doesn’t include the chart code at all, and the chart is only fetched when the user approaches it.
Frequently Asked Questions
Does incremental hydration work with Angular Universal?
Yes, it requires Angular SSR (which is the successor to Angular Universal). You need to enable provideClientHydration in your app config. Without SSR, there is no server-rendered HTML to hydrate, so the feature is meaningless.
Can I use @defer without SSR?
Yes, you can use @defer in a client-only app, but it won’t provide the same benefits. In a client-only app, the deferred block’s content is not rendered at all until the trigger fires, which means the user sees a placeholder instead of content. That can be useful for reducing initial bundle size, but it doesn’t improve hydration because there’s no SSR to begin with.
What’s the difference between on viewport and on idle?
on viewport fires when the element is scrolled into the visible area. on idle fires when the browser’s main thread is idle, which usually happens a few seconds after page load. Use on viewport for content that the user will likely see soon, and on idle for background tasks that can wait.
Can I have multiple triggers on one @defer block?
Yes, you can combine them with | for OR or with a comma for AND. For example, @defer (on viewport | on interaction) will hydrate when the element becomes visible OR when the user interacts with the placeholder. And @defer (on viewport, on timer(1000)) will hydrate only when both conditions are true.
Does incremental hydration affect SEO?
Because the server still sends the full HTML content (unless you use @placeholder that hides it), search engines can crawl the content. However, if you defer a block that contains important text, the placeholder might be empty, and the content is only rendered on the client after hydration. That could hurt SEO if the content is not in the initial HTML. For critical content, avoid deferring it.
How do I debug hydration issues?
Angular provides a hydration error message in the console when something goes wrong, such as mismatched HTML between server and client. You can also use the Angular DevTools to inspect which components are hydrated. Look for the “Hydration� tab in the profiler to see which parts of the tree are hydrated.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Mac Start Up Automatically After Power Failure?
- Expose Self-Hosted Service Securely Without Cloudflare Tunnel
- Nano Delivery for Functional Foods: What It Can and Can’t Do4.00
- Disable Apple Watch Turn-by-Turn Vibration While Driving
- Convert Play FLAC in iTunes: Full Guide
- Soda Foundation Charter: What It Means