Front-end angularforall.com

- Angular 19 : hydration incrémentale

Angular Angular 19 Hydration Ssr Performance
Angular 19 : hydration incrémentale

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()).

Problème : même avec l'hydration non-destructive d'Angular 16-18, Angular hydrate toute la page en un seul bloc. Pour une page e-commerce avec 50 composants, cela génère une longue tâche JavaScript qui bloque le fil principal — et fait grimper le TBT (Total Blocking Time).
// 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);
Prérequis Angular 19 : @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.

TriggerMoment d'hydrationCas d'usage idéal
hydrate on viewportQuand le composant entre dans le viewport (IntersectionObserver)Contenu sous le fold, sections longues
hydrate on idleQuand le navigateur est inactif (requestIdleCallback)Composants importants mais pas critiques
hydrate on hoverAu survol du composantDropdowns, mega-menus, tooltips
hydrate on interactionAu premier clic/toucher dans le composantFormulaires, panels accordéon
hydrate on timer(Xms)Après X millisecondesComposants à hydrater avec un délai fixe
hydrate when (condition)Quand la condition Signal devient vraieHydration conditionnelle au state
hydrate neverJamais — HTML statique définitifFooters 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étriqueHydration complèteHydration incrémentaleAmélioration
LCPInchangé (HTML déjà là)Inchangé ou meilleurNeutre à positif
TBTÉlevé (longue tâche JS)Faible (tâches courtes)-60% à -80%
TTIBloqué par l'hydration totaleRapide (seulement le critique)-40% à -60%
INPDégradé sous chargeStable (thread libre)Significatif
Bundle JS initial100% du code chargéCode critique seulement-30% à -70%
Combinaison gagnante pour Lighthouse 100 : hydration incrémentale + zoneless change detection (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() dans app.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 never sur les composants purement statiques
  • Valider les interactions au click avant hydration (file d'attente des events)

Partager