Appearance
Gérer la Compatibilité Client/Serveur en SSR avec Angular
Le Server-Side Rendering (SSR) dans Angular permet d'améliorer les performances et le SEO de votre application. Cependant, certaines fonctionnalités qui fonctionnent parfaitement côté client peuvent poser problème lors du rendu côté serveur.
Les erreurs les plus communes en SSR sont liées à l'accès aux API du navigateur qui n'existent pas côté serveur :
ReferenceError: localStorage is not defined
ReferenceError: document is not defined
ReferenceError: window is not defined
Solution avec PLATFORM_ID
PLATFORM_ID
est un token d'injection fourni par Angular qui permet d'identifier la plateforme sur laquelle votre application s'exécute. En combinaison avec la fonction isPlatformBrowser()
, il devient un outil essentiel pour gérer le code spécifique au navigateur.
Comprendre PLATFORM_ID
- En mode navigateur (client-side),
PLATFORM_ID
a la valeur 'browser' - En mode serveur (SSR),
PLATFORM_ID
a la valeur 'server'
La fonction isPlatformBrowser()
isPlatformBrowser()
est une fonction utilitaire qui :
- Prend
PLATFORM_ID
en paramètre - Retourne
true
si le code s'exécute dans le navigateur - Retourne
false
si le code s'exécute côté serveur
UTILISATION
C'est la méthode recommandée par Angular pour détecter l'environnement d'exécution de manière sûre et typée.
Autre approche
Une autre approche consiste à vérifier directement l'existence de l'objet window :
ts
export class BrowserCheckService {
isBrowser(): boolean {
return typeof window !== 'undefined';
}
}
Voici comment gérer correctement ces cas :
ts
import { inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { User } from './user.interface';
export class UserStorageService {
private platformId = inject(PLATFORM_ID);
saveUser(user: User): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem('currentUser', JSON.stringify(user));
}
}
getUser(): User | null {
if (isPlatformBrowser(this.platformId)) {
const userData = localStorage.getItem('currentUser');
return userData ? JSON.parse(userData) : null;
}
return null;
}
}
ts
import { Component, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Component({
selector: 'app-user',
standalone: true,
template: `
@if (showUserProfile) {
<div>
{{ user.name }}
</div>
}
`
})
export class UserComponent {
private platformId = inject(PLATFORM_ID);
checkBrowser(): void {
if (isPlatformBrowser(this.platformId)) {
// Code spécifique au navigateur
document.title = 'Profil Utilisateur';
}
}
}
Gestion des Librairies Tierces
CHARGEMENT DYNAMIQUE
Pour les librairies qui ne sont pas compatibles SSR, utilisez l'import dynamique :
ts
export class ChartComponent {
async loadChartLibrary() {
if (isPlatformBrowser(this.platformId)) {
const chartModule = await import('chart.js');
// Utilisation de la librairie
}
}
}
IMPORTANT
Assurez-vous toujours de tester votre application en mode SSR avant la mise en production pour détecter les problèmes de compatibilité client/serveur.
Bonus : Service de gestion de localStorage compatible SSR
Voici un service de gestion de localStorage compatible SSR, vous pouvez le réutiliser dans vos projets :
ts
import { Injectable, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { inject } from '@angular/core';
/**
* LocalStorageService
* Service to manage localStorage, SSR compatible
*
* @example
* const localStorage = inject(LocalStorageService);
* localStorage.setItem('key', 'value');
* localStorage.getItem<string>('key');
* localStorage.removeItem('key');
* localStorage.clear();
*/
@Injectable({
providedIn: 'root'
})
export class LocalStorageService {
private platformId = inject(PLATFORM_ID);
/**
* Set an item in localStorage
* @param key Storage key
* @param value Value to store
*/
setItem(key: string, value: unknown): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(key, JSON.stringify(value));
}
}
/**
* Get an item from localStorage
* @param key Storage key
* @returns The stored value or null if not found
*/
getItem<T>(key: string): T | null {
if (isPlatformBrowser(this.platformId)) {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
}
return null;
}
/**
* Remove an item from localStorage
* @param key Storage key
*/
removeItem(key: string): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(key);
}
}
/**
* Clear all items from localStorage
*/
clear(): void {
if (isPlatformBrowser(this.platformId)) {
localStorage.clear();
}
}
}