Lab 25 : Rendu côté serveur (SSR)
📖 Ressources
🚀 Code de départ
album-wholesale-v17-signal-pre-jest
Dans ce lab, vous activerez le rendu côté serveur dans l'application album wholesale. Le SSR pré-rend les pages sur le serveur, améliorant le temps de chargement initial et le référencement. Vous gérerez les API réservées au navigateur en toute sécurité, transférerez l'état pour éviter les double-fetches et exécuterez le serveur SSR localement.
💡 Fonctionnement du SSR
Avec le rendu côté client (CSR), le serveur envoie un squelette HTML vide — le navigateur reçoit <app-root></app-root> vide, puis télécharge et exécute le JavaScript pour construire la page.
Avec le rendu côté serveur (SSR), le serveur génère le HTML complet en premier. Le navigateur reçoit une page complète, affichée immédiatement et indexable par les moteurs de recherche. Angular hydrate ensuite la page — il attache les écouteurs d'événements et prend le relais sans re-rendu.
CSR : Serveur → HTML vide → navigateur télécharge JS → navigateur rend
SSR : Serveur rend le HTML → navigateur affiche immédiatement → Angular hydrate
📝 Instructions
Étape 1 : Ajouter le SSR au projet
ng add @angular/ssr
Ce schematic fait tout le travail. Il :
- Installe
@angular/ssretexpress - Crée
src/main.server.ts— point d'entrée du bootstrap côté serveur - Crée
server.ts— le serveur HTTP Express à la racine du projet - Crée
src/app/app.config.server.ts— providers spécifiques au serveur - Met à jour
angular.jsonavec les options de build"server"et"ssr" - Ajoute un script
serve:ssrdanspackage.json
Étape 2 : Examiner la configuration serveur
Inspectez le fichier généré app.config.server.ts :
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {
providers: [provideServerRendering()],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
Angular maintient deux configurations séparées : app.config.ts pour le navigateur et app.config.server.ts pour le serveur. La config serveur utilise mergeApplicationConfig pour hériter de tous les providers navigateur, puis ajoute provideServerRendering() par-dessus.
Cette séparation est importante : les providers réservés au navigateur comme provideStoreDevtools() (NgRx DevTools) restent uniquement dans app.config.ts — ils ne doivent jamais apparaître dans app.config.server.ts, où il n'y a pas d'environnement navigateur.
Étape 3 : Protéger le code réservé au navigateur
Le serveur n'a pas accès à window, localStorage ni document. Protégez le code navigateur avec isPlatformBrowser :
import { PLATFORM_ID, inject } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
export class AlbumDetailComponent {
private platformId = inject(PLATFORM_ID);
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
// Accès sécurisé à window, localStorage, etc.
const saved = localStorage.getItem('recentAlbums');
}
}
}
Étape 4 : Utiliser afterNextRender pour l'accès au DOM
Préférez afterNextRender (Angular 16+) aux hooks de cycle de vie pour les effets secondaires navigateur :
import { afterNextRender } from '@angular/core';
export class AlbumPlayerComponent {
constructor() {
afterNextRender(() => {
// S'exécute uniquement dans le navigateur, après le premier rendu
this.initAudioPlayer();
});
}
}
Les requêtes HTTP effectuées avec HttpClient pendant le SSR sont automatiquement transférées au navigateur via le mécanisme de transfer state d'Angular — le navigateur ne les re-fetch pas. Cela fonctionne automatiquement avec provideHttpClient(withFetch()) dans la config de l'application. Aucun code supplémentaire n'est nécessaire pour les appels API standards.
Étape 5 : Transférer l'état pour éviter le double fetch
Sans transfer state, Angular re-récupère les données dans le navigateur même si le serveur les a déjà récupérées. Corrigez cela avec TransferState :
import { TransferState, makeStateKey } from '@angular/core';
import { inject } from '@angular/core';
const ALBUMS_KEY = makeStateKey<Album[]>('albums');
export class AlbumService {
private transferState = inject(TransferState);
private http = inject(HttpClient);
getAlbums(): Observable<Album[]> {
const cached = this.transferState.get(ALBUMS_KEY, null);
if (cached) {
this.transferState.remove(ALBUMS_KEY);
return of(cached);
}
return this.http.get<Album[]>('/api/albums').pipe(
tap(albums => this.transferState.set(ALBUMS_KEY, albums))
);
}
}
Activez également l'hydratation dans app.config.ts :
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
provideClientHydration(),
],
};
Étape 6 : Construire et démarrer le serveur SSR
ng build
Cela génère deux dossiers de sortie dans dist/<nom-du-projet>/ :
browser/— ressources statiques servies au clientserver/— bundle Node.js
Astuce : Le chemin exact correspond à
outputPathdansangular.json. Vérifiez avecls dist/.
Démarrez le serveur :
node dist/album-wholesale-v17/server/server.mjs
Ouvrez http://localhost:4000 et vérifiez :
- Source de la page (Ctrl+U) — le HTML doit déjà contenir le contenu des albums
- Onglet Réseau — aucun appel API supplémentaire après l'hydratation
Étape 7 : Vérifier l'amélioration SEO
Ouvrez la source de la page et confirmez :
- La balise
<title>contient le nom de l'album - La méta description est présente
- Le HTML de la liste d'albums est visible dans la source (pas seulement
<app-root></app-root>)
Cela confirme que la page est rendue côté serveur avant d'être envoyée au navigateur.
✅ Validation en classe
La démonstration la plus claire que le SSR fonctionne : ouvrir DevTools → Réseau → rafraîchir la page et cliquer sur la requête du document HTML initial. Regarder l'onglet Réponse.
- CSR : on voit
<app-root></app-root>— vide, aucun contenu - SSR : on voit le HTML complet de la liste d'albums dans
<app-root>— rendu côté serveur
Aucun plugin nécessaire. Cette comparaison simple rend la valeur du SSR immédiatement tangible.