Appearance
DevOps pour les Service Workers Angular
Lorsque vous transformez votre application Angular en PWA, le service worker devient un élément central de votre infrastructure. Pour que tout fonctionne correctement en production, il est essentiel de suivre certaines bonnes pratiques DevOps. Ce tutoriel vous explique comment gérer le déploiement, les mises à jour, la sécurité et le monitoring de votre service worker.
Une analogie simple : la bibliothèque et son catalogue
Imaginez une bibliothèque qui utilise un système de catalogue pour gérer ses livres. Chaque fois qu'un nouveau livre arrive ou qu'un livre est retiré, le catalogue doit être mis à jour. Si le catalogue et les livres ne correspondent pas, les lecteurs risquent de chercher des livres qui n'existent plus ou de ne pas trouver les nouveaux livres disponibles.
Le service worker fonctionne de la même manière : il maintient un "catalogue" (le fichier ngsw.json) qui liste tous les fichiers de votre application. Si ce catalogue ne correspond pas aux fichiers réellement disponibles sur le serveur, votre application peut se comporter de manière inattendue, voire cesser de fonctionner.
1. Gérer correctement les mises à jour du service worker
Comprendre le processus de build
À chaque fois que vous exécutez ng build, Angular génère plusieurs éléments essentiels :
- De nouveaux fichiers : les fichiers JavaScript, CSS, HTML compilés avec leurs noms de version
- Un fichier de manifeste :
ngsw.jsonqui décrit toute la version de l'application - Des empreintes (hashes) : des identifiants uniques pour chaque fichier qui garantissent leur intégrité
Ces éléments forment un ensemble cohérent qui représente une version complète de votre application.
Les bonnes pratiques de déploiement
Pour que le service worker fonctionne correctement, vous devez :
Déployer tous les fichiers du build sans exception
Ne laissez jamais d'anciens fichiers sur le serveur. Si vous déployez une nouvelle version, remplacez complètement l'ancienne.
Remplacer l'ancien dossier par le nouveau
Évitez de mélanger des fichiers de différentes versions. Le service worker doit avoir une vision claire et cohérente des fichiers de la version actuelle.
S'assurer que le serveur ne sert jamais d'anciens fichiers mélangés avec des nouveaux
Un déploiement partiel peut causer des incohérences. Par exemple, si le nouveau ngsw.json référence des fichiers qui n'ont pas encore été déployés, le service worker ne pourra pas les trouver.
Déploiement atomique
Un déploiement doit être atomique : soit tous les fichiers sont déployés, soit aucun. Utilisez des techniques comme le déploiement dans un nouveau dossier suivi d'un changement de symlink, ou des outils qui garantissent la cohérence.
Exemple de script de déploiement
Voici un exemple de script bash qui garantit un déploiement atomique :
bash
#!/bin/bash
# Build de l'application
ng build --configuration production
# Création d'un dossier avec timestamp
TIMESTAMP=$(date +%s)
NEW_DIR="dist_$TIMESTAMP"
# Copie du build dans le nouveau dossier
cp -r dist/my-app "$NEW_DIR"
# Changement atomique du symlink
ln -sfn "$NEW_DIR" /var/www/my-app/current
# Nettoyage des anciens dossiers (garder les 5 derniers)
ls -dt dist_* | tail -n +6 | xargs rm -rf2. Permettre au service worker de détecter les nouvelles versions
Comment fonctionne la détection de version
Le service worker d'Angular fonctionne de manière intelligente :
- Il compare les fichiers actuels : à chaque chargement de l'application, il vérifie le fichier
ngsw.json - Il détecte s'il existe une nouvelle version : si les hashes des fichiers ont changé, une nouvelle version est disponible
- Il télécharge cette version en arrière-plan : sans interrompre l'utilisation de l'application
- Il applique proprement la nouvelle version : lors du prochain rechargement de la page
Déployer proprement vos builds
Pour que cette détection fonctionne :
Déployer proprement vos builds
Assurez-vous que chaque déploiement est complet et cohérent. Le fichier ngsw.json doit toujours correspondre aux fichiers présents sur le serveur.
Tester la mise à jour
Après chaque déploiement, testez que l'application détecte correctement la nouvelle version. Vous pouvez utiliser les DevTools Chrome :
- Ouvrez les DevTools (F12)
- Allez dans l'onglet Application
- Cliquez sur Service Workers
- Vérifiez l'état du service worker et forcez une mise à jour si nécessaire
Notifier l'utilisateur d'une nouvelle version
Bien que le service worker gère les mises à jour automatiquement, il est souvent préférable d'informer l'utilisateur qu'une nouvelle version est disponible et lui proposer de recharger la page.
Voici comment implémenter cette fonctionnalité :
typescript
import { Component, inject, signal } from '@angular/core';
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
import { filter } from 'rxjs/operators';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-root',
standalone: true,
template: `
@if (updateAvailable()) {
<div class="update-banner">
<p>Une nouvelle version de l'application est disponible.</p>
<button (click)="reloadPage()">Recharger</button>
</div>
}
<!-- Reste de votre application -->
`
})
export class AppComponent {
// Injection du service SwUpdate pour gérer les mises à jour
private swUpdate = inject(SwUpdate);
// Signal qui indique si une mise à jour est disponible
// Utilisation d'un signal pour une gestion réactive de l'état
updateAvailable = signal(false);
constructor() {
// Vérifier si le service worker est activé
if (this.swUpdate.isEnabled) {
// Écouter les événements de version prête
// takeUntilDestroyed() gère automatiquement le nettoyage à la destruction
this.swUpdate.versionUpdates
.pipe(
filter((evt): evt is VersionReadyEvent => evt.type === 'VERSION_READY'),
takeUntilDestroyed()
)
.subscribe(() => {
// Afficher la notification à l'utilisateur en mettant à jour le signal
this.updateAvailable.set(true);
});
// Vérifier périodiquement les mises à jour
this.swUpdate.checkForUpdate();
}
}
// Recharger la page pour activer la nouvelle version
reloadPage() {
window.location.reload();
}
}Vérification périodique
Vous pouvez également vérifier périodiquement les mises à jour en ajoutant un intervalle dans le constructeur :
typescript
constructor() {
// ... code existant ...
// Vérifier périodiquement les mises à jour toutes les minutes
if (this.swUpdate.isEnabled) {
setInterval(() => {
this.swUpdate.checkForUpdate();
}, 60000);
}
}3. S'assurer que les fichiers ne sont pas corrompus
Le système de vérification d'intégrité
Le service worker d'Angular utilise un système de hashes (empreintes) pour vérifier l'intégrité des fichiers. Chaque fichier a un hash unique calculé à partir de son contenu. Si le contenu change, le hash change aussi.
Lorsque le service worker télécharge un fichier, il compare le hash du fichier téléchargé avec le hash attendu dans ngsw.json. Si les hashes ne correspondent pas, le fichier est considéré comme corrompu.
Les bonnes pratiques de sécurité
Héberger l'application sous HTTPS
Les service workers nécessitent HTTPS pour fonctionner (sauf en localhost pour le développement). HTTPS garantit que les fichiers ne sont pas modifiés en transit entre le serveur et le navigateur.
Éviter les modifications manuelles
Ne modifiez jamais manuellement les fichiers dans le dossier dist après le build. Si vous devez modifier un fichier, modifiez le code source et relancez le build.
Éviter que les proxys ou CDNs modifient les fichiers
Certains proxys ou CDNs peuvent modifier les fichiers (compression, minification supplémentaire, injection de code...). Ces modifications changent les hashes et peuvent causer des problèmes.
Si vous utilisez un CDN ou un proxy, configurez-le pour qu'il ne modifie pas les fichiers Angular, ou désactivez la mise en cache pour les fichiers critiques.
Garder des fichiers versionnés cohérents
Un build partiellement déployé peut causer des incohérences. Par exemple, si ngsw.json référence un fichier avec un hash spécifique, mais que ce fichier n'a pas été déployé ou a été modifié, le service worker détectera une corruption.
Comportement en cas de corruption détectée
Si Angular détecte un fichier corrompu :
- Il désactive le cache : le service worker cesse d'utiliser le cache corrompu
- Il repasse en mode "normal" : l'application fonctionne comme une application web classique, sans les avantages du service worker
- Il protège l'utilisateur : plutôt que de servir du contenu potentiellement incorrect, Angular préfère désactiver le service worker
Désactivation automatique
Si le service worker se désactive automatiquement, c'est un signe qu'il y a un problème avec votre déploiement ou votre infrastructure. Vérifiez vos logs et votre processus de déploiement.
4. Gérer les erreurs et prévoir un "plan B"
Les problèmes courants
Un service worker peut rencontrer plusieurs types de problèmes :
- Cache corrompu : les données en cache sont invalides ou incohérentes
- Fichier illisible : un fichier référencé dans
ngsw.jsonn'existe pas ou n'est pas accessible - Version incohérente : les fichiers déployés ne correspondent pas à
ngsw.json - Déploiement incomplet : certains fichiers n'ont pas été déployés
Les bonnes pratiques de gestion d'erreurs
Ne jamais modifier le fichier ngsw.json
Le fichier ngsw.json est généré automatiquement par Angular lors du build. Ne le modifiez jamais manuellement. Toute modification peut causer des incohérences et des erreurs.
Surveiller l'enregistrement du service worker
Lors de chaque déploiement, vérifiez que le service worker s'enregistre correctement. Vous pouvez le faire via les DevTools ou en ajoutant des logs dans votre application :
typescript
import { SwUpdate } from '@angular/service-worker';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
// Dans votre composant ou service
constructor() {
if (this.swUpdate.isEnabled) {
// Écoute l'activation du service worker
// takeUntilDestroyed() gère automatiquement le nettoyage
this.swUpdate.activated
.pipe(takeUntilDestroyed())
.subscribe(() => {
console.log('Service worker activé avec succès');
});
// Écoute les nouvelles versions disponibles
this.swUpdate.available
.pipe(takeUntilDestroyed())
.subscribe(() => {
console.log('Nouvelle version disponible');
});
}
}Vider les caches en cas de problème
Si vous suspectez un problème de cache :
- Pour les développeurs : utilisez les DevTools Chrome → Application → Storage → Clear site data
- Pour les utilisateurs : guidez-les pour vider le cache de leur navigateur, ou implémentez une fonctionnalité dans votre application pour réinitialiser le service worker
Redéployer proprement l'application
Si un cache est endommagé ou si vous avez détecté une incohérence, la meilleure solution est de redéployer proprement l'application. Un nouveau build et un nouveau déploiement complet résoudront généralement les problèmes.
Implémentation d'un mécanisme de récupération
Vous pouvez implémenter un mécanisme qui permet de réinitialiser le service worker en cas de problème :
typescript
import { Injectable, inject } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
@Injectable({
providedIn: 'root'
})
export class ServiceWorkerService {
private swUpdate = inject(SwUpdate);
/**
* Réinitialise le service worker en désenregistrant l'ancien
* et en forçant un rechargement complet
*/
async resetServiceWorker(): Promise<void> {
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
// Désenregistrer tous les service workers
for (const registration of registrations) {
await registration.unregister();
}
// Vider tous les caches
if ('caches' in window) {
const cacheNames = await caches.keys();
await Promise.all(
cacheNames.map(cacheName => caches.delete(cacheName))
);
}
// Recharger la page pour réenregistrer le service worker
window.location.reload();
}
}
}5. Vérifier l'état du service worker
Les outils de monitoring
Angular fournit plusieurs moyens de vérifier l'état du service worker :
Les DevTools Chrome
- Ouvrez les DevTools (F12)
- Allez dans l'onglet Application
- Cliquez sur Service Workers dans le menu de gauche
- Vous verrez l'état du service worker, sa version, et vous pourrez le mettre à jour ou le désactiver
Les outils internes d'Angular
Angular expose des endpoints spéciaux pour le debugging (si activés) :
/ngsw/state: affiche l'état actuel du service worker/ngsw/state.json: retourne l'état au format JSON
Pour activer ces endpoints en développement, vous pouvez ajouter cette configuration dans votre app.config.ts :
typescript
import { provideServiceWorker } from '@angular/service-worker';
import { isDevMode } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
// ... autres providers
provideServiceWorker('ngsw-worker.js', {
enabled: !isDevMode(),
registrationStrategy: 'registerWhenStable:30000'
})
]
};Ce qu'il faut vérifier à chaque déploiement
Lors de chaque déploiement, vérifiez que :
Le service worker s'enregistre correctement
Ouvrez les DevTools et vérifiez que le service worker apparaît dans la liste et qu'il est actif.
Les caches se créent
Dans les DevTools → Application → Cache Storage, vérifiez que les caches Angular sont créés et contiennent les fichiers attendus.
L'application fonctionne hors ligne
- Activez le mode offline dans les DevTools (Network → Offline)
- Rechargez la page
- Vérifiez que l'application fonctionne toujours
Exemple de composant de monitoring
Vous pouvez créer un composant de monitoring pour afficher l'état du service worker dans votre application :
typescript
import { Component, inject, signal } from '@angular/core';
import { SwUpdate, VersionEvent } from '@angular/service-worker';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-sw-status',
standalone: true,
template: `
<div class="sw-status">
<h3>État du Service Worker</h3>
<p>Activé : {{ isEnabled() ? 'Oui' : 'Non' }}</p>
<p>Version actuelle : {{ currentVersion() }}</p>
<p>Nouvelle version disponible : {{ updateAvailable() ? 'Oui' : 'Non' }}</p>
@if (updateAvailable()) {
<button (click)="activateUpdate()">Activer la mise à jour</button>
}
</div>
`
})
export class ServiceWorkerStatusComponent {
private swUpdate = inject(SwUpdate);
// Signal qui indique si le service worker est activé
// Cette valeur est statique et peut être définie directement
isEnabled = signal(this.swUpdate.isEnabled);
// Signal pour stocker la version actuelle
currentVersion = signal<string>('Inconnue');
// Signal qui indique si une mise à jour est disponible
updateAvailable = signal(false);
constructor() {
if (this.isEnabled()) {
// Écoute les événements de version
// takeUntilDestroyed() gère automatiquement le nettoyage
this.swUpdate.versionUpdates
.pipe(takeUntilDestroyed())
.subscribe((event: VersionEvent) => {
if (event.type === 'VERSION_READY') {
// Met à jour les signaux avec les nouvelles informations
this.updateAvailable.set(true);
this.currentVersion.set(event.latestVersion.hash);
}
});
// Écoute l'activation pour récupérer la version actuelle
// On peut utiliser toSignal pour obtenir la dernière valeur activée
this.swUpdate.activated
.pipe(takeUntilDestroyed())
.subscribe((update) => {
if (update?.current) {
this.currentVersion.set(update.current.hash);
}
});
}
}
// Activer la nouvelle version
async activateUpdate() {
if (this.swUpdate.isEnabled) {
await this.swUpdate.activateUpdate();
window.location.reload();
}
}
}6. Bien préparer l'environnement de déploiement
Les exigences techniques
Pour que votre PWA fonctionne correctement, votre environnement de déploiement doit respecter certaines exigences :
Servir l'application sous HTTPS
Les service workers nécessitent HTTPS (sauf en localhost). Assurez-vous que votre serveur est configuré avec un certificat SSL valide.
Servir correctement les fichiers statiques
Votre serveur doit être capable de servir tous les fichiers statiques (JS, CSS, images, etc.) avec les bons headers HTTP. Les fichiers doivent être accessibles directement via leur URL.
Éviter la réécriture ou la compression incorrecte
Certains serveurs ou proxys peuvent réécrire ou compresser les fichiers. Cela peut modifier les hashes et causer des problèmes. Configurez votre serveur pour qu'il ne modifie pas les fichiers Angular.
Configurer le fallback vers index.html
Pour les routes Angular (routing côté client), votre serveur doit retourner index.html pour toutes les routes qui ne correspondent pas à des fichiers statiques. C'est ce qu'on appelle le "fallback".
Exemple de configuration serveur
Voici des exemples de configuration pour différents serveurs :
Nginx
nginx
server {
listen 80;
server_name example.com;
root /var/www/my-app;
index index.html;
# Servir les fichiers statiques
location / {
try_files $uri $uri/ /index.html;
}
# Headers pour les fichiers statiques
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Headers spécifiques pour ngsw.json (ne pas mettre en cache)
location = /ngsw.json {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}Apache (.htaccess)
apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
# Headers pour ngsw.json
<FilesMatch "ngsw\.json$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
</FilesMatch>Node.js avec Express
typescript
import express from 'express';
import path from 'path';
const app = express();
const distPath = path.join(__dirname, 'dist/my-app');
// Servir les fichiers statiques
app.use(express.static(distPath));
// Fallback vers index.html pour les routes Angular
app.get('*', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Headers HTTP importants
Assurez-vous que votre serveur envoie les bons headers HTTP :
Content-Typecorrect pour chaque type de fichierCache-Controlapproprié (les fichiers Angular peuvent être mis en cache longtemps, maisngsw.jsonne doit pas être mis en cache)
Déploiement propre et cohérent
Le point le plus important : chaque déploiement doit être propre, complet et cohérent.
Cela signifie :
- Tous les fichiers sont déployés en même temps
- Aucun fichier de l'ancienne version ne reste sur le serveur
- Le fichier
ngsw.jsoncorrespond exactement aux fichiers présents - Le déploiement est atomique (tout ou rien)
7. Comprendre la logique du cache
Comment fonctionne le cache Angular
Le service worker d'Angular met en cache différents types de ressources :
Les fichiers critiques
Les fichiers JavaScript, CSS, HTML de votre application sont automatiquement mis en cache. Ces fichiers sont essentiels au fonctionnement de l'application.
Les assets selon la configuration
Les autres assets (images, polices, etc.) sont mis en cache selon la configuration dans ngsw-config.json. Vous pouvez spécifier quels fichiers mettre en cache et comment.
Configuration du cache
Le fichier ngsw-config.json vous permet de configurer le comportement du cache :
json
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/*.css",
"/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2)"
]
}
}
]
}Les bonnes pratiques de cache
Mettre en cache uniquement ce qui est utile
Ne mettez pas en cache des fichiers qui changent fréquemment ou qui ne sont pas nécessaires hors ligne. Cela gaspille de l'espace de stockage et peut causer des problèmes de synchronisation.
Éviter de mettre en cache des fichiers dynamiques
Les fichiers générés dynamiquement (comme les images uploadées par les utilisateurs, les données API) ne doivent généralement pas être mis en cache par le service worker. Utilisez plutôt le cache HTTP standard ou des stratégies spécifiques.
Tester les comportements hors ligne
Après chaque déploiement, testez que l'application fonctionne correctement hors ligne :
- Chargez l'application une fois en ligne
- Activez le mode offline dans les DevTools
- Rechargez la page
- Vérifiez que l'application fonctionne toujours
