2. Le problème des stores sur le serveur
Le concept Svelte de store est pratique car il permet de stocker des données réactives accessibles par tout composant d'une application Svelte.
Pour rappel, on appelle store un état réactif global, que ce soit un store de Svelte 4, ou un état global de Svelte 5, comme vu dans ce chapitre,
Mais cela peut se révéler problématique avec SvelteKit, car SvelteKit permet de travailler sur le serveur.
Voyons un piège classique lié à l'utilisation d'un store avec SvelteKit : les données de user.
On souhaite pouvoir accéder aux données du user partout dans notre application. Un store user
semble donc adapté.
1. Je crée un store
Supposons que l'on crée un store user
contenant les informations d'une personne. Un store est
pertinent car on souhaite afficher ces valeurs à différents endroits de la page.
// stores.svelte.ts
function createUser() {
let score = $state();
function set(u) {
user = u;
}
return {
get value() {
return score;
},
set
};
}
export const user = createUser(); // le store `user` est instancié une fois pour toute l'application
Ce store est accessible partout, dans tous les composants ainsi que dans tous les fichiers .ts
ou
.js
, ce qui est pratique.
Mais si vous utilisez les options par défaut de SvelteKit, vous avez du rendu côté serveur, ce qui signifie que vos pages sont potentiellement d'abord construites sur le serveur. Et donc que votre store sera instancié sur le serveur, et maintenu tant que le process Node tourne.
2. Je mets à jour mon store sur le client
Ça ne pose probablement pas de problème si ce store n'est jamais vraiment utilisé sur le serveur, car en général on met à jour ce genre de données sur le client lors d'interactions comme des clics.
<script>
import { user } from '$lib/stores.svelte';
</script>
<!-- ici on est sûr de mettre à jour `user` uniquement sur le client -->
<button onclick={() => user.set({ name: 'romain' })}>Remplir le store</button>
La valeur du store instancié sur le serveur reste donc à sa valeur initiale – undefined
dans notre
cas, ce qui ne pose pas vraiment de problème autre qu'un peu de mémoire inutilement allouée.
3. Je récupère des données depuis le serveur
Imaginez que vous récupériez le score de la personne depuis le serveur, par exemple grâce à la
fonction load
d'un +layout.server.ts
.
// +layout.server.ts
export async function load() {
const user = await getUserData(); // on devrait utiliser les cookies, mais on simplifie ici
return {
user
};
}
4. Je mets à jour mon store dans le layout
Puis, pour rendre cette valeur accessible et manipulable par tous les composants, vous stockez cette
valeur dans votre store dès que vous la recevez côté client, donc dans le +layout.svelte
.
<!-- +layout.svelte -->
<script>
import { user } from '$lib/stores.svelte';
const { data } = $props();
user.set(data.user);
</script>
Tout va bien, vous avez les données de la personne accessible partout côté client. Mais vous avez
également stocké ces données côté serveur, car le fichier +layout.svelte
est exécuté côté
serveur lors de la première requête...
5. J'ai perdu
Sans le savoir, vous avez rendu accessibles des données personnelles à toute personne se connectant sur votre serveur SvelteKit et qui tenterait de lire le contenu de ce store.
Même si le contenu du store sur le serveur est écrasé à chaque nouvelle requête, cela pose un problème de sécurité majeur, car une intrusion sur le serveur permettrait d'accéder à la valeur du store à tout moment.
Solutions ?
Il y a plusieurs solutions à ce problème :
onMount
Le problème vient du fait que l'on a mis à jour un store depuis le serveur. Donc si on fait attention à ne pas mettre à jour le store depuis le serveur, tout va bien.
Pour cela, on peut utiliser onMount
, qui est une méthode de cycle de
vie uniquement exécutée sur le client.
<!-- +layout.svelte -->
<script>
import { onMount } from 'svelte';
import { user } from '$lib/stores.svelte';
const { data } = $props();
onMount(() => {
user.set(data.user); // tout va bien, je suis côté client
});
</script>
Néanmoins, cela reste une mauvaise idée :
- la possibilité de mettre à jour ailleurs ce store tout en étant côté serveur reste ouverte
- les éléments nécessitant les données du user ne sont pas rendus côté serveur, nécéssitant une re-rendu côté client
Les données de layout sont accessibles partout
On souhaite rendre disponibles nos données de user
dans toute notre application. Il se trouve que
les données de layout sont rendues disponibles
par SvelteKit dans toutes les pages concernées par le layout.
En utilisant cette propriété sur le layout racine – qu'il est impossible d'esquiver –, il est donc possible de rendre disponible des données dans toutes les pages de l'application.
<!-- +page.svelte -->
<script>
import { onMount } from 'svelte';
const { data } = $props(); // `data` contient les données de layout
</script>
<p>{data.user.name}</p>
Il faudra tout de même passer ces informations aux composants qui en ont besoin, ou bien utiliser le store de page qui contient également les données de layout, sans présenter de risque de fuites de données.
Bien sûr cette dernière solution ne permet pas telle quelle de mettre à jour nos données dans toute
l'application comme le ferait un store. Néanmoins, on peut supposer que la mise à jour de telles
données – celles de l'utilisateur dans notre cas – vont nécessiter une requête au serveur. En
utilisant les fonctionnalités des fonctions
load
, on peut assez facilement mettre à jour
nos données globalement sans utiliser de store.
Stores et Contexte
La solution ci-dessus, pourtant adaptée, ne vous satisfait pas ? Vous souhaitez tout de même pouvoir mettre à jour vos données globalement côté client sans nécessiter un appel au serveur ? Bien.
Il existe une technique pour utiliser les stores de manière sécurisée, grâce aux Contextes de Svelte, comme montré dans cette vidéo. Il s'agit néanmoins d'une technique avancée que nous ne traiterons pas ici.
En conclusion
De manière générale, avec SvelteKit, éviter d'utiliser des stores pour stocker de l'information sensible.
Passez plutôt par les données de layout.