Communitygithub.com

agentmemoir/compétences

Construisez, migrez ou auditez des applications Svelte 5 rendues côté serveur utilisant les runes ($state, $derived, $effect, $props, $bindable) avec SSR SvelteKit ou Vite.

Qu'est-ce que compétences ?

Cette compétence est conçue pour les développeurs qui travaillent avec Svelte 5 en mode rendu côté serveur (SSR), que ce soit avec SvelteKit ou Vite + Svelte. Elle couvre les défis spécifiques liés à l'utilisation des runes dans un contexte SSR : la double exécution des composants (serveur puis client), les pièges comme `$effect` qui ne s'exécute jamais sur le serveur, les états partagés entre requêtes à l'échelle du module, et les décalages d'hydratation causés par des valeurs non déterministes. Les instructions pas à pas aident à choisir la bonne architecture, à connaître le comportement de chaque rune pendant le SSR, à isoler les états par requête via `load` et le contexte, et à respecter le contrat d'hydratation. Elle fournit également des règles de sécurité SSR (protection des secrets, CSP, XSS, CSRF) et une méthodologie d'audit complète via des fichiers de référence. Cette compétence fonctionne aussi bien pour des projets neufs que pour des audits ou le débogage d'applications existantes, avec des vérifications de sécurité et des corrections de bugs liés au SSR.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/agentmemoir/skills/tree/HEAD/skills/frontend/svelte5-ssr-runes

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Rendu côté serveur Svelte 5 (Runes)

Ce que couvre cette compétence

« SSR » signifie que chaque requête est rendue en HTML par un serveur (Node, Bun, serverless ou edge) avant que le navigateur ne la reçoive ; les mêmes composants hydratent ensuite l'application interactive. Contrairement à une construction uniquement statique, un vrai serveur existe au moment de la requête, donc +page.server.js, +layout.server.js, +server.js, les actions de formulaire et hooks.server.js sont autorisés — et c'est là que les données et les secrets doivent être placés.

Le côté « runes » est ce qui rend le SSR délicat : chaque composant s'exécute deux fois, une fois sur le serveur et une fois dans le navigateur. Un code parfaitement valide dans une application client seul se brise ici :

  • $effect ne s'exécute jamais sur le serveur, donc tout état qu'il initialise est absent du HTML serveur.
  • Les $state au niveau module dans les fichiers .svelte.ts sont créés une fois par processus serveur et partagés entre toutes les requêtes des utilisateurs.
  • Les valeurs calculées à partir de Date.now(), Math.random() ou localStorage pendant le rendu diffèrent entre serveur et client, provoquant des décalages d'hydratation.

Ces trois échecs représentent la majorité des bugs SSR dans le code utilisant les runes, et chacun a un petit modèle correct ci-dessous.

Étape 1 — Choisir la forme du projet

BesoinUtiliser
Application complète : routes, chargement de données serveur, formulaires, auth, streamingSvelteKit avec adapter-auto (détection automatique Vercel/Netlify/Cloudflare), adapter-node (auto-hébergement), ou un adaptateur de plateforme. C'est le défaut ; l'utiliser sauf s'il y a une raison de ne pas le faire.
Un arbre de composants Svelte dans un backend existant (Express/Hono/Fastify/Bun)Vite + Svelte 5 simple : render() de svelte/server côté serveur, hydrate() dans le navigateur. Vous gérez le routage, les en-têtes et le traitement des requêtes.
Contenu majoritairement statique avec quelques routes dynamiquesSvelteKit avec export const prerender = true par route (ou 'auto' pour pré-rendre les pages populaires et rendre côté serveur la longue traîne). Les routes dynamiques suivent toujours chaque règle ici.

Ne définissez pas export const ssr = false pour « corriger » un bug SSR — cela transforme la route en une coque rendue côté client (une SPA), perdant le SEO et le premier affichage. Ce n'est légitime que pour les pages qui ne peuvent vraiment pas se rendre sans globaux navigateur. csr = false est le commutateur inverse : HTML serveur sans hydratation, pour les pages qui n'ont besoin d'aucune interactivité.

Les étapes de configuration pour les deux chemins, ainsi que les adaptateurs et les options de page, se trouvent dans references/ssr-build.md. Lisez-le avant de générer svelte.config.js / vite.config.js, car les noms d'options et le comportement des adaptateurs diffèrent selon les versions et sont faciles à mal comprendre subtilement.

Étape 2 — Connaître les runes qui s'exécutent sur le serveur

Ce tableau est le cœur de la compétence :

RunePendant le SSRNotes
$state(v)S'exécuteLa valeur initiale est rendue. La muter en cours de rendu ne re-rend pas le HTML déjà émis — le SSR est un passage unique.
$state.raw(v)S'exécutePas de proxy. Bon pour les grandes charges data que vous remplacez en bloc plutôt que de les muter.
$derived(expr)S'exécuteÉvalué dans le cadre du rendu, donc sa sortie atterrit dans le HTML. Gardez-le pur et sans API navigateur uniquement.
$effect / $effect.preJamaisNavigateur uniquement. L'état qu'il définit est absent du HTML SSR (apparition après hydratation). Ce n'est pas une erreur d'hydratation, mais c'est du HTML vide et un décalage de mise en page visible.
$effect.root(fn)JamaisIdem. Ne construisez pas d'état visible par le serveur à l'intérieur.
$props() / $bindable()S'exécuteLes données chargées arrivent comme props ; les valeurs initiales sont rendues.
$props.id()S'exécuteID unique compatible SSR, cohérent entre serveur et client (Svelte ≥ 5.20). Utilisez-le pour les liens for/aria-labelledby.
$inspect(v)Dev uniquementPas de sortie SSR.
$state.snapshot(v)S'exécuteUtilisez avant de passer l'état à une API non Svelte ou une bibliothèque serveur.
$effect.tracking()falseLe compilateur serveur le remplace par le littéral false ; il n'y a pas de contexte de tracking pendant le SSR.

Le bug SSR classique que ce tableau évite :

<!-- ❌ vide dans le HTML serveur, puis apparaît après hydratation -->
<script>
  let user = $state();
  $effect(() => { user = getUser(); });
</script>
<div>{user?.name}</div>

La correction est presque toujours la même : calculer les valeurs visibles par le SSR dans $derived, ou les récupérer dans load et les passer comme props. Gardez $effect pour les choses qui n'existent que dans le navigateur (canvas, observateurs, analytique, synchronisation de stockage).

Lisez references/runes-ssr.md pour le comportement serveur complet rune par rune, les modèles de valeurs compatibles avec l'hydratation et le statut expérimental du SSR asynchrone.

Étape 3 — Garder l'état par requête, jamais global au module

// ❌ un seul objet partagé pour tout le processus serveur — l'utilisateur A peut voir les données de l'utilisateur B
// src/lib/user.svelte.ts
export const user = $state({ name: '' });

La correction canonique est de retourner les données par requête depuis load et de les transmettre comme props ; lorsque de nombreux composants imbriqués en ont besoin, placez-les dans un contexte avec un accesseur :

<!-- +layout.svelte -->
<script>
  import { setContext } from 'svelte';
  let { data } = $props();
  setContext('user', () => data.user);
</script>
<!-- tout enfant -->
<script>
  import { getContext } from 'svelte';
  const user = getContext('user');
</script>
<p>Bonjour {user().name}</p>

Le contexte est attaché à l'arbre des composants, et chaque requête SSR obtient son propre arbre, donc il ne peut pas fuir entre utilisateurs. Passez une fonction (ou un objet avec des accesseurs) dans setContext, pas la valeur elle-même, sinon la réactivité est perdue. Les données par requête dont de nombreuses fonctions serveur ont besoin peuvent vivre sur event.locals, renseignées dans hooks.server.ts.

L'état au niveau module reste acceptable pour l'état d'application côté client uniquement qu'aucune donnée utilisateur ne touche (thème, indicateurs de fonctionnalité), tant que rien ne rend côté serveur des valeurs personnalisées à partir de celui-ci.

Étape 4 — Traiter l'hydratation comme un contrat

L'hydratation rejoue le HTML serveur ; le premier rendu du client doit correspondre. Toute valeur différente provoque une hydration_mismatch, qui n'est délibérément pas suppressible — corrigez le code, pas l'avertissement.

  • Rendez de manière déterministe : pas de Date.now(), pas de formatage new Date(), pas de Math.random(), pas de crypto.randomUUID(), pas de lectures localStorage/window/navigator dans le chemin de rendu.
  • Générez des IDs avec $props.id(), pas avec des compteurs aléatoires ou modulaires.
  • Formatez les dates sur le serveur dans load et passez des chaînes, ou passez toujours un timeZone explicite — les fuseaux horaires du serveur et du client diffèrent.
  • Valeurs côté client uniquement (stockage, requêtes média, géolocalisation) : commencez par une valeur par défaut compatible serveur et remplissez dans $effect/onMount, ou conditionnez le balisage avec un indicateur mounted avec une solution de repli stable côté serveur.
<script>
  import { browser } from '$app/environment';

  let theme = $state('light');
  $effect(() => {
    if (!browser) return;
    theme = localStorage.getItem('theme') ?? 'light'; // après montage, donc l'hydratation correspond
  });
</script>

En SSR Svelte simple (sans SvelteKit), $app/environment n'est pas disponible — utilisez typeof window !== 'undefined' dans un effet, jamais dans le chemin de rendu.

Étape 5 — Charger et diffuser des données sur le serveur

  • +page.server.ts / +layout.server.ts pour les bases de données, les fichiers et les variables d'environnement privées ; +page.ts universel pour les API publiques qui peuvent s'exécuter dans les deux environnements.
  • La fonction load serveur doit retourner des données qui survivent à la sérialisation devalue : JSON plus Date, Map, Set, BigInt, RegExp et les références cycliques. Pas de fonctions ni d'instances de classe.
  • Données lentes, non essentielles : retournez la promesse et utilisez {#await} dans la page — les promesses imbriquées sont diffusées au fur et à mesure de leur résolution. Attendez les données essentielles avant de retourner pour qu'elles soient dans le premier affichage.
  • Utilisez event.fetch dans load : il hérite des cookies de la requête et intègre les réponses pour l'hydratation au lieu de les re-récupérer.
  • Après la navigation, data se met à jour sans recréer le composant : dérivez-en ($derived) au lieu de les copier dans des variables locales non réactives.

Le SSR asynchrone (await dans les composants) et les fonctions distantes sont encore expérimentaux à partir de Svelte 5.57 / SvelteKit 2.7x (septembre 2026). N'activez pas compilerOptions.experimental.async dans le code de production sauf si je le demande explicitement ; le statut exact et l'option d'activation se trouvent dans references/runes-ssr.md.

Étape 6 — Appliquer les règles de sécurité SSR

  1. Les secrets restent privés. $env/static/private / $env/dynamic/private sont réservés au serveur ; seules les variables préfixées par PUBLIC_ atteignent le navigateur. Les imports depuis $lib/server/ et *.server.js lèvent une erreur si un composant les importe.
  2. Tout ce qui est retourné par load est intégré dans la charge utile HTML — intégralement, pas seulement les champs que vous rendez. Ne retournez jamais de mots de passe hashés, jetons ou champs internes dont vous n'avez pas besoin côté client.
  3. CSP avec nonces. Configurez kit.csp ; SvelteKit injecte un nonce dans ses propres scripts en ligne, et %sveltekit.nonce% couvre les balises manuelles dans app.html. Un vrai serveur peut envoyer de vrais en-têtes CSP — utilisez cela au lieu d'une balise <meta> plus faible.
  4. {@html} reste une XSS. Être rendu côté serveur ne signifie pas être assaini. Assainissez le HTML non fiable avec DOMPurify immédiatement avant le rendu.
  5. L'authentification appartient à hooks.server.ts (remplissez locals) plus des vérifications au niveau des routes. Les fonctions load de layout ne s'exécutent pas à chaque navigation côté client.
  6. La protection CSRF est activée par défaut pour les soumissions de formulaire ; laissez-la activée, et vérifiez l'origine vous-même dans tout point de terminaison de mutation +server.js personnalisé.
  7. Cookies de session : httpOnly, secure en production, sameSite : 'lax' au minimum.
  8. Un fetch côté serveur d'URLs fournies par l'utilisateur est une SSRF. Autorisez les hôtes sur une liste blanche ; bloquez les IP privées et les points de terminaison de métadonnées cloud.

Liste de contrôle complète et code : references/security.md.

Auditer une application existante

Lorsque la tâche est d'auditer ou de déboguer plutôt que de construire, parcourez references/ssr-review.md : vérifications du mode de rendu, runes invisibles côté serveur, risques d'hydratation, état limité à la requête, exposition de la charge utile, vérifications de sécurité et simulations de pannes. Signalez les résultats sous forme fichier:ligne avec la panne de production que chacun cause.

Fichiers de référence

  • references/runes-ssr.md — comportement serveur rune par rune, modèles de sécurité pour l'hydratation, état limité à la requête, rendu svelte/server + hydrate simple, et statut du SSR asynchrone / fonctions distantes expérimental.
  • references/ssr-build.md — configuration SvelteKit et adaptateurs, options de page (ssr/csr/prerender), hooks, actions de formulaire, streaming, SSR Vite simple, et notes de déploiement.
  • references/ssr-review.md — liste de contrôle d'audit pour les applications SSR existantes : modes de rendu, runes invisibles côté serveur, risques d'hydratation, cadrage de l'état, exposition de la charge utile, vérifications de sécurité et simulations de pannes.
  • references/security.md — secrets et variables d'environnement, nonces CSP, XSS, CSRF, gestion auth/session, SSRF, fuite d'erreurs, et une liste de contrôle avant mise en production.
  • assets/ — fichiers de démarrage copiables : svelte.config.js (SvelteKit avec CSP), hooks.server.ts (auth plus en-têtes de sécurité), et index.html / entry-server.js / entry-client.js pour le SSR Vite simple (le trio est construit en CI par tests/plain-ssr).

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

Skills associés