Aller au contenu

Lab 25 : Rendu côté serveur (SSR)

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.

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
Fenêtre de terminal
ng add @angular/ssr

Ce schematic fait tout le travail. Il :

  • Installe @angular/ssr et express
  • 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.json avec les options de build "server" et "ssr"
  • Ajoute un script serve:ssr dans package.json

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);

Étape 3 : Protéger le code réservé au navigateur

Section intitulée « É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

Section intitulée « É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();
});
}
}

Étape 5 : Transférer l’état pour éviter le double fetch

Section intitulée « É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(),
],
};
Fenêtre de terminal
ng build

Cela génère deux dossiers de sortie dans dist/<nom-du-projet>/ :

  • browser/ — ressources statiques servies au client
  • server/ — bundle Node.js

Astuce : Le chemin exact correspond à outputPath dans angular.json. Vérifiez avec ls dist/.

Démarrez le serveur :

Fenêtre de terminal
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

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.

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.