Aller au contenu principal

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/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

É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);
Deux configs fusionnées

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();
});
}
}
HttpClient côté serveur

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 client
  • server/ — bundle Node.js

Astuce : Le chemin exact correspond à outputPath dans angular.json. Vérifiez avec ls 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.