localStorage : synchronisation entre onglets
Plusieurs onglets de la même origine partagent le même localStorage. L’événement storage prévient les autres onglets quand une valeur change.
L’événement storage
Section intitulée « L’événement storage »window.addEventListener('storage', (evenement) => { console.log(evenement.key); // clé modifiée (null si clear()) console.log(evenement.oldValue); // ancienne valeur (null si nouvelle clé) console.log(evenement.newValue); // nouvelle valeur (null si supprimée) console.log(evenement.url); // page qui a fait la modification console.log(evenement.storageArea); // l'objet localStorage concerné});Règle essentielle : l’événement se déclenche dans les autres onglets et fenêtres de l’origine, pas dans celui qui a écrit. Un onglet doit donc mettre à jour son propre affichage directement, et réagir à storage pour les changements des autres.
Exemple : thème synchronisé
Section intitulée « Exemple : thème synchronisé »function appliquerTheme(theme) { document.documentElement.dataset.theme = theme;}
function changerTheme(theme) { appliquerTheme(theme); // onglet courant try { localStorage.setItem('theme', theme); } catch {} // déclenche 'storage' ailleurs}
window.addEventListener('storage', (e) => { if (e.key === 'theme' && e.newValue) appliquerTheme(e.newValue);});Si l’utilisateur change le thème dans un onglet, les autres suivent immédiatement.
Exemple : déconnexion dans tous les onglets
Section intitulée « Exemple : déconnexion dans tous les onglets »function deconnecter() { localStorage.setItem('deconnexion', String(Date.now())); // signal localStorage.removeItem('deconnexion'); allerALaPageDeConnexion();}
window.addEventListener('storage', (e) => { if (e.key === 'deconnexion' && e.newValue) allerALaPageDeConnexion();});Écrire une valeur différente à chaque fois : l’événement ne se déclenche que si la valeur change.
Cas où storage ne se déclenche pas
Section intitulée « Cas où storage ne se déclenche pas »| Situation | Résultat |
|---|---|
| Écriture d’une valeur identique à l’actuelle | Aucun événement |
| Écriture dans l’onglet courant | Aucun événement dans cet onglet |
| Origines différentes (domaine, port, schéma) | Stockages séparés, pas d’événement |
| Onglet en navigation privée et onglet normal | Stockages séparés |
sessionStorage |
Événement seulement entre iframe du même onglet |
BroadcastChannel : un meilleur outil pour les messages
Section intitulée « BroadcastChannel : un meilleur outil pour les messages »BroadcastChannel envoie des messages entre contextes de la même origine, sans passer par le stockage :
const canal = new BroadcastChannel('monapp');
canal.postMessage({ type: 'theme', valeur: 'dark' });
canal.onmessage = (e) => { if (e.data.type === 'theme') appliquerTheme(e.data.valeur);};
// à la fermeturecanal.close();storage |
BroadcastChannel |
|
|---|---|---|
| Conserve la donnée | Oui | Non (message seulement) |
| Déclenché dans l’onglet émetteur | Non | Non |
| Données échangées | Chaînes | Objets (clonage structuré) |
| Usage | Réagir à un changement de réglage mémorisé | Signaux, notifications, coordination |
Combiner les deux est courant : stocker la valeur dans localStorage (mémoire), la diffuser par BroadcastChannel (signal).
Éviter les conflits d’écriture
Section intitulée « Éviter les conflits d’écriture »Deux onglets peuvent lire une valeur, la modifier et l’écrire presque en même temps : la dernière écriture écrase l’autre (perte de mise à jour).
// Risqué : lecture puis écriture non atomiquesconst panier = lire('panier', []);panier.push(article);ecrire('panier', panier);Solutions :
- Relire juste avant d’écrire, et limiter la durée de la modification.
- Écrire un élément par clé (
panier:42) plutôt qu’une liste dans une seule clé. - Verrouiller avec l’API Web Locks :
await navigator.locks.request('panier', async () => { const panier = lire('panier', []); panier.push(article); ecrire('panier', panier);});navigator.locks.request garantit qu’un seul onglet exécute le bloc à la fois pour un nom de verrou donné (disponible dans les navigateurs récents).
Réagir aux changements dans un framework
Section intitulée « Réagir aux changements dans un framework »// Hook générique (React)import { useSyncExternalStore } from 'react';
function souscrire(callback) { window.addEventListener('storage', callback); return () => window.removeEventListener('storage', callback);}
export function useStockage(cle, defaut) { return useSyncExternalStore( souscrire, () => localStorage.getItem(cle) ?? defaut, () => defaut, // valeur côté serveur );}Voir Avec Astro, React et Next.js.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
| L’événement ne se déclenche jamais | Onglet qui a écrit (déclenche ailleurs), valeur identique, autre origine |
| L’onglet courant ne se met pas à jour | Mettre à jour directement l’affichage lors de l’écriture |
| Valeur obsolète après une écriture dans un autre onglet | Écouter storage et relire |
| Perte de données avec deux onglets | Écritures non atomiques : une clé par élément, ou navigator.locks |
e.newValue est null |
Clé supprimée ou clear() appelé |
BroadcastChannel sans réponse |
Noms de canaux différents, ou contexte d’origine différente |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.