Implémentez le Server-Side Rendering avec Angular Universal et maîtrisez l'hydration pour améliorer les performances et le SEO de votre application.
Pourquoi le SSR
Par défaut, Angular génère une application 100% client-side. Le navigateur reçoit un HTML quasi-vide et exécute le JavaScript pour construire la page. Cela pose deux problèmes :
- SEO : les moteurs de recherche peuvent avoir du mal à indexer le contenu JavaScript
- Performance (FCP/LCP) : l'utilisateur voit une page blanche jusqu'à ce que le JS soit chargé et exécuté
Le SSR résout ces problèmes en rendant le HTML complet côté serveur dès la première requête.
@angular/ssr remplace Angular Universal et s'intègre directement dans le CLI Angular.
Installation de @angular/ssr
Pour ajouter le SSR à un projet existant :
# Ajouter @angular/ssr au projet
ng add @angular/ssr
# Ou lors de la création d'un nouveau projet
ng new my-app --ssr
Angular CLI génère automatiquement :
server.ts— le serveur Expressapp.config.server.ts— configuration spécifique au serveur- Mise à jour de
angular.jsonavec les cibles de build SSR
Configuration serveur
// app.config.ts (client)
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideClientHydration(), // active l'hydration
]
};
// app.config.server.ts (serveur)
import { mergeApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
const serverConfig = {
providers: [
provideServerRendering()
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
L'hydration complète
Sans hydration, Angular détruisait et recréait entièrement le DOM au chargement client, causant un flash de contenu. Depuis Angular 16, provideClientHydration() permet à Angular de réutiliser le DOM existant rendu par le serveur.
// Avec hydration complète : Angular ne recrée pas le DOM
// Il "adopte" le HTML serveur et y attache les event listeners
// Vérifier que l'hydration fonctionne :
// Les DevTools Angular indiquent le statut d'hydration de chaque composant
// Désactiver l'hydration pour un composant spécifique (rare)
@Component({
...
providers: [{ provide: HYDRATION_SKIP, useValue: true }]
})
provideClientHydration() manuellement.
TransferState : partager les données
Un problème classique du SSR : les appels HTTP sont effectués côté serveur, puis répétés côté client. TransferState (ou le HTTP interceptor withHttpTransferCache) évite cette double requête :
// app.config.ts
import { provideClientHydration, withHttpTransferCache } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideClientHydration(
withHttpTransferCache()
),
]
};
Avec withHttpTransferCache(), Angular sérialise les réponses HTTP dans le HTML SSR. Côté client, les mêmes URLs retournent immédiatement depuis le cache, sans nouvelle requête réseau.
Pièges courants du SSR
Le serveur Node.js n'a pas accès aux APIs du navigateur. Voici les erreurs fréquentes :
// ERREUR : window n'existe pas côté serveur
ngOnInit() {
const width = window.innerWidth; // ReferenceError!
}
// SOLUTION : vérifier la plateforme
import { isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID, inject } from '@angular/core';
ngOnInit() {
if (isPlatformBrowser(inject(PLATFORM_ID))) {
const width = window.innerWidth; // OK
}
}
- Ne jamais accéder à
window,document,localStoragedirectement - Toujours vérifier avec
isPlatformBrowser()ouafterNextRender() - Utiliser
afterNextRender()pour le code qui doit s'exécuter uniquement côté client - Attention aux librairies tierces qui accèdent au DOM directement
// afterNextRender() : s'exécute uniquement côté client après le rendu
import { afterNextRender } from '@angular/core';
export class ChartComponent {
constructor() {
afterNextRender(() => {
// Ici on peut accéder à window, document, etc.
this.initChart();
});
}
}