Exploitez l'hydration incrémentale d'Angular 19 pour charger les composants progressivement et améliorer les performances de vos applications en mode.
L'hydration complète et ses limites de performance
Le SSR Angular génère du HTML côté serveur. Quand le navigateur reçoit cette page, le HTML est affiché immédiatement (bon pour le FCP/LCP), mais la page n'est pas encore interactive — c'est l'état "déshydraté".
L'étape d'hydration consiste à attacher les event listeners, initialiser les services Angular, et connecter le JavaScript aux nœuds DOM existants sans les recréer (disponible depuis Angular 16 avec provideClientHydration()).
// Angular 16-18 : hydration complète en un bloc
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(), // hydrate TOUT à la fois
provideRouter(routes),
]
};
// Résultat : une longue tâche JS qui bloque le thread principal
// pour les pages avec beaucoup de composants
Fonctionnement de l'hydration incrémentale
L'hydration incrémentale (Angular 19, developer preview) change fondamentalement l'approche :
- Le serveur rend le HTML complet — y compris les composants dans les blocs
@defer. Le SEO est entièrement préservé. - Le client charge le JS critique uniquement pour les composants immédiatement interactifs.
- Les composants différés restent déshydratés — leur HTML est visible, mais pas encore interactif.
- L'hydration se déclenche à la demande selon des triggers (viewport, hover, interaction, idle...).
// Différence fondamentale : SSR avec hydration incrémentale
//
// AVANT (hydration complète) :
// Server → HTML complet
// Client → hydrate tout immédiatement → longue tâche JS
//
// APRÈS (hydration incrémentale) :
// Server → HTML complet (y compris @defer)
// Client → hydrate seulement le critique immédiatement
// Client → hydrate le footer quand scroll → viewport
// Client → hydrate le mega-menu quand hover
// Client → hydrate les commentaires quand click
//
// Résultat : le fil principal n'est jamais saturé
Configuration dans Angular 19
L'hydration incrémentale est activée via withIncrementalHydration(). L'application doit avoir @angular/ssr configuré — elle ne produit aucun effet en client-only.
// app.config.ts — Angular 19
import { ApplicationConfig } from '@angular/core';
import {
provideClientHydration,
withIncrementalHydration,
} from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideClientHydration(
withIncrementalHydration() // Active l'hydration incrémentale
),
]
};
// app.config.server.ts — config serveur SSR (inchangée)
import { ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
import { mergeApplicationConfig } from '@angular/core';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
@angular/ssr dans les dépendances, ng add @angular/ssr si pas encore configuré. L'hydration incrémentale est en developer preview — stable pour tester, pas encore pour production critique.
@defer avec hydration incrémentale — syntaxe complète
Sans withIncrementalHydration() : le serveur rend le @placeholder pour les blocs @defer. Avec : le serveur rend le vrai contenu, et le client l'hydrate progressivement.
<!-- template d'un composant Angular 19 avec SSR -->
<!-- Section critique : hydratée immédiatement -->
<app-hero-section />
<app-navigation />
<!-- Section dans le viewport initial : hydratée au chargement -->
@defer (hydrate on idle) {
<app-product-carousel [products]="featuredProducts" />
}
<!-- Section sous le fold : hydratée en entrant dans le viewport -->
@defer (on viewport; hydrate on viewport) {
<app-product-grid [category]="currentCategory" />
} @loading {
<div class="skeleton-grid"></div>
}
<!-- Section footer : visible mais jamais interactive -->
@defer (hydrate never) {
<app-static-footer />
<!-- HTML rendu par le serveur, jamais hydraté côté client -->
<!-- Parfait pour les footers purement informatifs -->
}
Tous les triggers d'hydration disponibles
Les triggers d'hydration (hydrate on ...) contrôlent quand Angular télécharge le JavaScript du composant différé et l'hydrate.
| Trigger | Moment d'hydration | Cas d'usage idéal |
|---|---|---|
hydrate on viewport | Quand le composant entre dans le viewport (IntersectionObserver) | Contenu sous le fold, sections longues |
hydrate on idle | Quand le navigateur est inactif (requestIdleCallback) | Composants importants mais pas critiques |
hydrate on hover | Au survol du composant | Dropdowns, mega-menus, tooltips |
hydrate on interaction | Au premier clic/toucher dans le composant | Formulaires, panels accordéon |
hydrate on timer(Xms) | Après X millisecondes | Composants à hydrater avec un délai fixe |
hydrate when (condition) | Quand la condition Signal devient vraie | Hydration conditionnelle au state |
hydrate never | Jamais — HTML statique définitif | Footers statiques, bannières, textes informatifs |
<!-- Exemples complets pour chaque trigger -->
<!-- Hydrater après un délai fixe (chatterbots, bandeau cookies) -->
@defer (on timer(3000ms); hydrate on timer(3000ms)) {
<app-cookie-banner />
}
<!-- Hydrater selon un Signal (ex: utilisateur connecté) -->
@defer (when isLoggedIn(); hydrate when isLoggedIn()) {
<app-user-dashboard />
}
<!-- Combinaison : charger au viewport, hydrater à l'interaction -->
@defer (on viewport; hydrate on interaction) {
<app-comments-section [postId]="postId" />
} @placeholder {
<p>Cliquez pour charger les commentaires</p>
}
Stratégies par type de composant
La bonne stratégie d'hydration dépend du rôle du composant dans la page.
Composants critiques — jamais différés
<!-- Navbar, hero, CTA principal : hydratés immédiatement (pas de @defer) -->
<app-navbar />
<app-hero [headline]="headline" [cta]="primaryCta" />
<app-search-bar />
<!-- Ces composants sont critiques pour l'interaction immédiate -->
Composants visibles mais secondaires — idle
<!-- Carousel, recommandations : visible mais pas immédiatement cliqué -->
@defer (hydrate on idle) {
<app-featured-products />
<app-promotions-banner />
}
<!-- Hydraté pendant les frames libres — pas de blocage du thread principal -->
Contenu sous le fold — viewport
<!-- Sections longues de contenu, grid de produits -->
@defer (on viewport; hydrate on viewport) {
<app-product-grid [filters]="activeFilters" />
<app-review-list [productId]="productId" />
}
<!-- Hydraté seulement si l'utilisateur scrolle jusqu'ici -->
Footer et contenu purement statique — never
<!-- Footer avec liens, texte légal, plan du site -->
@defer (hydrate never) {
<app-footer />
}
<!-- HTML rendu par le serveur, aucun JS chargé côté client
Économise ~30-50KB de JS selon la complexité du footer -->
Impact mesurable sur les Core Web Vitals
| Métrique | Hydration complète | Hydration incrémentale | Amélioration |
|---|---|---|---|
| LCP | Inchangé (HTML déjà là) | Inchangé ou meilleur | Neutre à positif |
| TBT | Élevé (longue tâche JS) | Faible (tâches courtes) | -60% à -80% |
| TTI | Bloqué par l'hydration totale | Rapide (seulement le critique) | -40% à -60% |
| INP | Dégradé sous charge | Stable (thread libre) | Significatif |
| Bundle JS initial | 100% du code chargé | Code critique seulement | -30% à -70% |
provideExperimentalZonelessChangeDetection()) + ChangeDetectionStrategy.OnPush sur tous les composants.
Pièges et limitations connues
Piège 1 : Interactions pendant la déshydratation
<!-- PROBLÈME : l'utilisateur clique avant l'hydration du composant -->
@defer (on viewport; hydrate on viewport) {
<app-add-to-cart [product]="product" />
}
<!-- Angular 19 met les événements en file d'attente -->
<!-- Le click est rejoué une fois l'hydration terminée -->
<!-- VÉRIFIER que le comportement est cohérent pour votre cas -->
Piège 2 : Components avec ViewChild / ContentChild
// Si un composant parent référence un composant déshydraté avec ViewChild,
// la référence sera undefined jusqu'à l'hydration
@Component({ template: `
@defer (hydrate on interaction) {
<app-chart #chart />
}
`})
class DashboardComponent implements AfterViewInit {
@ViewChild('chart') chart?: ChartComponent; // undefined avant hydration!
ngAfterViewInit() {
// chart peut être undefined si pas encore hydraté
this.chart?.drawData(this.data); // safe navigation operator obligatoire
}
}
Piège 3 : Angular 19 developer preview
L'hydration incrémentale est en developer preview dans Angular 19. L'API peut changer dans Angular 20. Ne pas utiliser en production sans validation complète.
Migrer de l'hydration complète vers incrémentale
La migration est progressive et non-destructive — withIncrementalHydration() est backward compatible. Les composants sans @defer continuent de s'hydrater normalement.
Étapes de migration recommandées
- Ajouter
withIncrementalHydration()dansapp.config.ts - Identifier les composants "sous le fold" avec les DevTools Angular et Lighthouse
- Wrapper les composants non-critiques avec
@defer (hydrate on viewport) - Tester le rendu serveur avec
ng serve --ssr - Mesurer le TBT et l'INP avec Lighthouse avant/après
- Ajouter
hydrate neversur les composants purement statiques - Valider les interactions au click avant hydration (file d'attente des events)