Appearance
Server-Side Rendering (SSR) avec Angular
Le Server-Side Rendering est une technique puissante qui permet de générer le contenu de votre application côté serveur avant de l'envoyer au navigateur. Mais qu'est-ce que cela signifie concrètement ?
Comprendre le SSR avec un exemple concret
Imaginez un restaurant avec deux façons de servir :
- Sans SSR (Client-Side Rendering) : Le client reçoit une boîte vide et tous les ingrédients séparément. Il doit assembler lui-même son plat.
- Avec SSR : Le client reçoit son plat déjà préparé et assemblé, prêt à être consommé.
Pourquoi utiliser le SSR ?
AVANTAGES
- Meilleur référencement (SEO)
- Temps de chargement initial plus rapide
- Meilleure performance sur les appareils peu puissants
- Prévisualisation plus efficace sur les réseaux sociaux
Installation et Configuration
Option 1 : Nouveau projet avec SSR
bash
ng new my-app --ssr
Option 2 : Ajouter le SSR à un projet existant
bash
ng add @angular/ssr
ATTENTION
Assurez-vous d'avoir Angular 17+ pour utiliser les dernières fonctionnalités SSR
Structure du projet SSR
ts
// Importation des dépendances nécessaires
import { APP_BASE_HREF } from '@angular/common'; // Pour gérer l'URL de base de l'application
import { CommonEngine } from '@angular/ssr'; // Moteur SSR d'Angular
import express from 'express'; // Framework web pour Node.js
import { fileURLToPath } from 'node:url'; // Pour convertir les URL en chemins de fichiers
import { dirname, join, resolve } from 'node:path'; // Pour manipuler les chemins de fichiers
import bootstrap from './src/main.server'; // Point d'entrée de l'application Angular côté serveur
// Fonction principale qui configure et retourne l'application Express
export function app(): express.Express {
const server = express();
// Configuration des chemins des dossiers
const serverDistFolder = dirname(fileURLToPath(import.meta.url)); // Dossier de distribution du serveur
const browserDistFolder = resolve(serverDistFolder, '../browser'); // Dossier de distribution du navigateur
const indexHtml = join(serverDistFolder, 'index.server.html'); // Fichier HTML principal
// Création du moteur SSR Angular
const commonEngine = new CommonEngine();
// Configuration du moteur de vue Express
server.set('view engine', 'html');
server.set('views', browserDistFolder);
// Gestion des fichiers statiques
// Tous les fichiers dans le dossier browser seront servis statiquement
server.get('**', express.static(browserDistFolder, {
maxAge: '1y', // Cache d'un an pour les fichiers statiques
index: 'index.html',
}));
// Gestion des routes Angular
server.get('**', (req, res, next) => {
// Extraction des informations de la requête
const { protocol, originalUrl, baseUrl, headers } = req;
// Rendu de l'application avec le moteur SSR
commonEngine
.render({
bootstrap, // Point d'entrée de l'application
documentFilePath: indexHtml, // Chemin vers le fichier HTML principal
url: `${protocol}://${headers.host}${originalUrl}`, // URL complète de la requête
publicPath: browserDistFolder, // Chemin vers les assets publics
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], // Configuration de l'URL de base
})
.then((html) => res.send(html)) // Envoi du HTML généré au client
.catch((err) => next(err)); // Gestion des erreurs
});
return server;
}
// Fonction de démarrage du serveur
function run(): void {
const port = process.env['PORT'] || 4000; // Port d'écoute (4000 par défaut)
// Démarrage du serveur Express
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Lancement de l'application
run();
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);
ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';
const bootstrap = () => bootstrapApplication(AppComponent, config);
export default bootstrap;
Lisez les commentaires pour comprendre le code. Nous allons se concentrer sur le fichier server.ts
, et principalement sur commonEngine
:
Explication de CommonEngine
La Route Universelle
typescript
server.get('**', (req, res, next) => {
EXPLICATION
Le **
signifie que ce middleware intercepte TOUTES les requêtes qui n'ont pas été traitées par les middlewares précédents. C'est notre "filet de sécurité" qui assure que toutes les routes Angular seront gérées.
Extraction des Informations de Requête
typescript
const { protocol, originalUrl, baseUrl, headers } = req;
Décomposons chaque élément :
protocol
: 'http' ou 'https'originalUrl
: le chemin complet demandé (ex: '/users/profile')baseUrl
: l'URL de base de l'applicationheaders
: les en-têtes de la requête, dontheaders.host
(ex: 'localhost:4000')
Configuration du Rendu
typescript
commonEngine.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
})
Option | Description | Exemple |
---|---|---|
bootstrap | Le point d'entrée de votre application Angular défini dans main.server.ts. C'est la fonction qui initialise l'application côté serveur. | bootstrap importé depuis main.server.ts |
documentFilePath | Le chemin vers le template HTML qui servira de base pour le rendu. C'est généralement le fichier index.html de votre application. | src/index.html |
url | L'URL complète reconstituée à partir du protocole, de l'hôte et du chemin. Elle permet au routeur Angular de déterminer quelle page générer. | https://localhost:4000/users/profile |
publicPath | Le chemin vers le dossier contenant les fichiers statiques de l'application (CSS, images, JavaScript, etc.). C'est généralement le dossier de build. | dist/browser |
providers | Configuration supplémentaire pour l'injection de dépendances Angular. Permet notamment de définir l'URL de base avec APP_BASE_HREF et d'autres services nécessaires au SSR. | [{ provide: APP_BASE_HREF, useValue: baseUrl }] |
Gestion de la Réponse
typescript
.then((html) => res.send(html)) // Succès : envoi du HTML
.catch((err) => next(err)); // Erreur : passage au middleware d'erreur
- Le moteur SSR génère le HTML complet de la page
- En cas de succès : le HTML est envoyé au navigateur
- En cas d'erreur : l'erreur est transmise au gestionnaire d'erreurs d'Express