Communitygithub.com

Svelte 5 Server-Side Rendering (Runes)

Erstellen, migrieren oder überprüfen Sie serverseitig gerenderte Svelte 5-Apps mit Runes ($state, $derived, $effect, $props, $bindable) und SvelteKit SSR (adapter-auto/node/cloudflare/vercel/netlify) oder einfachem Vite SSR mit svelte/server render() und hydrate().

Was ist Svelte 5 Server-Side Rendering (Runes)?

Dieses Skill ist für Entwickler gedacht, die mit Svelte 5 und serverseitigem Rendering (SSR) arbeiten. Es behandelt die besonderen Herausforderungen, die Runes in SSR-Umgebungen mit sich bringen – insbesondere, dass Komponenten sowohl auf dem Server als auch im Browser ausgeführt werden. Der Schwerpunkt liegt auf der Vermeidung typischer SSR-Fehler wie fehlende Zustände durch $effect, fälschlicherweise modulweit geteilte Zustände und Hydrierungsinkonsistenzen. Das Skill unterstützt zwei Hauptprojekttypen: vollständige SvelteKit-Apps mit Adaptern (auto, node, cloudflare, vercel, netlify) und einfache Vite + Svelte 5-Setups ohne Routing. Es bietet eine schrittweise Anleitung zur korrekten Verwendung von Runes, zur Sicherstellung der Hydrierungstreue, zum request-scoped State-Management, zur serverseitigen Datenladung und zu SSR-Sicherheitsregeln. Darüber hinaus enthält es Referenzdateien für Build-Optionen, Sicherheits-Checklisten und Audit-Prozesse für bestehende SSR-Apps. Der Skill wird automatisch aktiviert, wenn Svelte 5 SSR, SvelteKit load-Funktionen, Hydrierungsprobleme oder SSR-sicherheitsrelevante Code-Stellen erwähnt werden.

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

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht Svelte 5 Server-Side Rendering (Runes)?

Was dieser Skill abdeckt

„SSR” bedeutet, dass jede Anfrage von einem Server (Node, Bun, Serverless oder Edge) in HTML gerendert wird, bevor der Browser sie erhält; dieselben Komponenten hydrieren dann zu einer interaktiven App. Anders als bei einem rein statischen Build existiert zur Anfragezeit ein echter Server, daher sind +page.server.js, +layout.server.js, +server.js, Formularaktionen und hooks.server.js erlaubt – und genau dort gehören Daten und Geheimnisse hin.

Der Teil mit den Runes macht SSR knifflig: Jede Komponente läuft zweimal, einmal auf dem Server und einmal im Browser. Code, der in einer client-seitigen App einwandfrei funktioniert, bricht hier:

  • $effect wird auf dem Server nie ausgeführt, daher fehlt jeder von ihm gesetzte Zustand im Server-HTML.
  • Modulweites $state in .svelte.ts-Dateien wird einmal pro Serverprozess erstellt und über alle Benutzeranfragen hinweg geteilt.
  • Werte, die während des Renderns aus Date.now(), Math.random() oder localStorage berechnet werden, unterscheiden sich zwischen Server und Client, was zu Hydrierungsfehlern führt.

Diese drei Fehler sind für die meisten SSR-Bugs in Runes-Code verantwortlich, und jedes hat ein kleines, korrektes Muster weiter unten.

Schritt 1 – Wählen Sie die Projektform

BedarfVerwendung
Vollständige App: Routen, serverseitiges Laden von Daten, Formulare, Authentifizierung, StreamingSvelteKit mit adapter-auto (Vercel/Netlify/Cloudflare automatisch), adapter-node (selbst gehostet) oder einem Plattform-Adapter. Dies ist der Standard; verwenden Sie ihn, es sei denn, es gibt einen Grund, es nicht zu tun.
Ein Svelte-Komponentenbaum innerhalb eines bestehenden Backends (Express/Hono/Fastify/Bun)Einfaches Vite + Svelte 5: render() von svelte/server auf dem Server, hydrate() im Browser. Sie kümmern sich um Routing, Header und Anfrageverarbeitung.
Größtenteils statische Inhalte mit wenigen dynamischen RoutenSvelteKit mit export const prerender = true pro Route (oder 'auto', um beliebte Seiten zu prerendern und den Langschwanz serverseitig zu rendern). Die dynamischen Routen folgen dennoch jeder hier stehenden Regel.

Setzen Sie nicht export const ssr = false, um einen SSR-Fehler zu „beheben” – das verwandelt die Route in eine client-seitig gerenderte Hülle (eine SPA), wodurch SEO und der erste Paint verloren gehen. Es ist nur für Seiten legitim, die wirklich nicht ohne Browser-Globals gerendert werden können. csr = false ist der umgekehrte Schalter: Server-HTML ohne Hydrierung für Seiten, die keine Interaktivität benötigen.

Einrichtungsschritte für beide Pfade sowie Adapter und Seitenoptionen finden Sie in references/ssr-build.md. Lesen Sie es, bevor Sie svelte.config.js / vite.config.js generieren, da Optionsnamen und Adapterverhalten je nach Version unterschiedlich sind und leicht subtil falsch gemacht werden können.

Schritt 2 – Wissen, welche Runes auf dem Server laufen

Diese Tabelle ist der Kern des Skills:

RuneWährend SSRAnmerkungen
$state(v)LäuftDer Anfangswert wird gerendert. Eine Mutation während des Renderns führt nicht zu einem erneuten Rendern des bereits ausgegebenen HTML – SSR ist ein Durchlauf.
$state.raw(v)LäuftKein Proxy. Gut für große data-Payloads, die Sie vollständig ersetzen, anstatt zu mutieren.
$derived(expr)LäuftWird als Teil des Renderns ausgewertet, sein Ergebnis landet daher im HTML. Halten Sie es rein und frei von Browser-Only-APIs.
$effect / $effect.preNieNur Browser. Der von ihm gesetzte Zustand fehlt im SSR-HTML (Pop-in nach Hydrierung). Dies ist kein Hydrierungsfehler, aber es ist leeres HTML und sichtbarer Layout-Shift.
$effect.root(fn)NieGleich. Bauen Sie keinen server-sichtbaren Zustand darin auf.
$props() / $bindable()LäuftGeladene Daten kommen als Props an; Anfangswerte rendern.
$props.id()LäuftSSR-sichere eindeutige ID, konsistent zwischen Server und Client (Svelte ≥ 5.20). Verwenden Sie sie für for/aria-labelledby-Links.
$inspect(v)Nur DevKeine SSR-Ausgabe.
$state.snapshot(v)LäuftVerwenden Sie es, bevor Sie den Zustand an eine Nicht-Svelte-API oder Serverbibliothek übergeben.
$effect.tracking()falseDer Server-Compiler ersetzt es mit dem Literal false; es gibt während SSR keinen Tracking-Kontext.

Der klassische SSR-Fehler, den diese Tabelle verhindert:

<!-- ❌ leer im Server-HTML, taucht dann nach der Hydrierung auf -->
<script>
  let user = $state();
  $effect(() => { user = getUser(); });
</script>
<div>{user?.name}</div>

Die Behebung ist fast immer dieselbe: Berechnen Sie SSR-sichtbare Werte in $derived, oder holen Sie sie in load und übergeben Sie sie als Props. Behalten Sie $effect für Dinge, die nur im Browser existieren (Canvas, Observer, Analysen, Storage-Sync).

Lesen Sie references/runes-ssr.md für das vollständige Rune-für-Rune-Serververhalten, hydrierungssichere Wertemuster und den experimentellen asynchronen SSR-Status.

Schritt 3 – Zustand pro Anfrage, niemals modulglobal

// ❌ Ein gemeinsames Objekt für den gesamten Serverprozess – Benutzer A kann Daten von Benutzer B sehen
// src/lib/user.svelte.ts
export const user = $state({ name: '' });

Die kanonische Behebung besteht darin, pro-Anfrage-Daten aus load zurückzugeben und als Props nach unten zu reichen. Wenn viele verschachtelte Komponenten sie benötigen, legen Sie sie mit einem Getter in den Kontext:

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

Kontext ist am Komponentenbaum befestigt, und jede SSR-Anfrage erhält ihren eigenen Baum, daher kann er nicht zwischen Benutzern durchsickern. Übergeben Sie eine Funktion (oder ein Objekt mit Gettern) an setContext, nicht den Wert selbst, sonst geht die Reaktivität verloren. Pro-Anfrage-Daten, die viele Serverfunktionen benötigen, können auf event.locals leben, befüllt in hooks.server.ts.

Modulweiter Zustand ist weiterhin in Ordnung für rein client-seitigen App-Zustand, den keine Benutzerdaten berühren (Theme, Feature-Flags), solange nichts personalisierte Werte daraus serverseitig rendert.

Schritt 4 – Hydrierung als Vertrag behandeln

Hydrierung spielt das Server-HTML erneut ab; das erste Rendern des Clients muss damit übereinstimmen. Jeder abweichende Wert verursacht eine hydration_mismatch, die bewusst nicht unterdrückbar ist – beheben Sie den Code, nicht die Warnung.

  • Rendern Sie deterministisch: kein Date.now(), keine new Date()-Formatierung, kein Math.random(), kein crypto.randomUUID(), keine localStorage/window/navigator-Lesevorgänge im Render-Pfad.
  • Generieren Sie IDs mit $props.id(), nicht mit Zufälligkeit oder Modulzählern.
  • Formatieren Sie Daten auf dem Server in load und übergeben Sie Zeichenketten, oder übergeben Sie immer eine explizite timeZone – Server- und Client-Zeitzonen unterscheiden sich.
  • Client-Only-Werte (Storage, Media Queries, Geolocation): Starten Sie mit einem server-sicheren Standardwert und füllen Sie ihn innerhalb von $effect/onMount aus, oder schließen Sie Markup hinter einem mounted-Flag mit einem stabilen Server-Fallback.
<script>
  import { browser } from '$app/environment';

  let theme = $state('light');
  $effect(() => {
    if (!browser) return;
    theme = localStorage.getItem('theme') ?? 'light'; // nach dem Mount, damit Hydrierung übereinstimmt
  });
</script>

In einfachem Svelte SSR (ohne SvelteKit) ist $app/environment nicht verfügbar – verwenden Sie typeof window !== 'undefined' innerhalb eines Effekts, niemals im Render-Pfad.

Schritt 5 – Daten auf dem Server laden und streamen

  • +page.server.ts / +layout.server.ts für Datenbanken, Dateien und private Umgebungsvariablen; universelles +page.ts für öffentliche APIs, die an beiden Orten laufen können.
  • Server-load muss Daten zurückgeben, die die Devalue-Serialisierung überstehen: JSON plus Date, Map, Set, BigInt, RegExp und zyklische Referenzen. Keine Funktionen oder Klasseninstanzen.
  • Langsame, nicht essentielle Daten: Geben Sie das Promise zurück und {#await} es in der Seite – verschachtelte Promises streamen, während sie aufgelöst werden. Warten Sie essentielle Daten ab, bevor Sie zurückkehren, damit sie im ersten Paint enthalten sind.
  • Verwenden Sie event.fetch innerhalb von load: Es erbt die Anfrage-Cookies und inlined Antworten für die Hydrierung anstatt erneut zu laden.
  • Nach der Navigation aktualisiert sich data, ohne die Komponente neu zu erstellen: Leiten Sie davon ab ($derived), anstatt es in nicht-reaktive Locals zu kopieren.

Asynchrones SSR (await in Komponenten) und Remote-Funktionen sind ab Svelte 5.57 / SvelteKit 2.7x (September 2026) noch experimentell. Aktivieren Sie compilerOptions.experimental.async nicht in Produktionscode, es sei denn, ich frage explizit danach; der genaue Status und die Opt-In-Möglichkeit finden sich in references/runes-ssr.md.

Schritt 6 – Die SSR-Sicherheitsregeln anwenden

  1. Geheimnisse bleiben privat. $env/static/private / $env/dynamic/private sind nur-server; nur mit PUBLIC_-Präfix versehene Variablen erreichen den Browser. Importe von $lib/server/ und *.server.js werfen einen Fehler, wenn eine Komponente sie einbindet.
  2. Alles, was von load zurückgegeben wird, ist im HTML-Payload eingebettet – alles, nicht nur die Felder, die Sie rendern. Geben Sie niemals Passwort-Hashes, Tokens oder interne Felder zurück, die Sie auf dem Client nicht benötigen.
  3. CSP mit Nonces. Konfigurieren Sie kit.csp; SvelteKit injiziert einen Nonce in seine eigenen Inline-Skripte, und %sveltekit.nonce% deckt manuelle Tags in app.html ab. Ein echter Server kann echte CSP-Header senden – verwenden Sie das anstelle eines schwächeren <meta>-Tags.
  4. {@html} ist immer noch XSS. Serverseitig gerendert bedeutet nicht bereinigt. Bereinigen Sie nicht vertrauenswürdiges HTML mit DOMPurify unmittelbar vor dem Rendern.
  5. Authentifizierung gehört in hooks.server.ts (befüllt locals) plus routenbezogene Prüfungen. Layout-load-Funktionen laufen nicht bei jeder client-seitigen Navigation.
  6. CSRF-Schutz ist standardmäßig aktiviert für Formularübermittlungen; lassen Sie ihn aktiviert und prüfen Sie den Origin selbst in jedem benutzerdefinierten +server.js-Mutationsendpunkt.
  7. Session-Cookies: httpOnly, secure in Produktion, sameSite: 'lax' mindestens.
  8. Server-seitiges fetch von benutzerbereitgestellten URLs ist SSRF. Erlauben Sie Hosts; blockieren Sie private IPs und Cloud-Metadaten-Endpunkte.

Vollständige Checkliste und Code: references/security.md.

Überprüfen einer bestehenden App

Wenn es um die Überprüfung oder Fehlersuche geht, nicht um den Bau, arbeiten Sie references/ssr-review.md durch: Rendering-Modus-Prüfungen, server-unsichtbare Runes, Hydrierungsrisiken, request-scoped State, Payload-Exposition, Sicherheits-Greps und Fehler-Durchläufe. Berichten Sie Ergebnisse als Datei:Zeile mit dem Produktionsfehler, den jeder verursacht.

Referenzdateien

  • references/runes-ssr.md – Rune-für-Rune-Serververhalten, Hydrierungssicherheitsmuster, request-scoped State, einfaches svelte/server render + hydrate und der experimentelle asynchrone SSR / Remote-Funktionen-Status.
  • references/ssr-build.md – SvelteKit-Setup und Adapter, Seitenoptionen (ssr/csr/prerender), Hooks, Formularaktionen, Streaming, einfaches Vite SSR und Bereitstellungshinweise.
  • references/ssr-review.md – Audit-Checkliste für bestehende SSR-Apps: Rendering-Modi, server-unsichtbare Runes, Hydrierungsrisiken, State-Scoping, Payload-Exposition, Sicherheits-Greps und Fehler-Durchläufe.
  • references/security.md – Geheimnisse und Umgebungsvariablen, CSP-Nonces, XSS, CSRF, Auth/Session-Handling, SSRF, Fehlerlecks und eine Pre-Ship-Checkliste.
  • assets/ – kopierbare Startdateien: svelte.config.js (SvelteKit mit CSP), hooks.server.ts (Auth plus Sicherheitsheader) und index.html / entry-server.js / entry-client.js für einfaches Vite SSR (das Trio wird in CI durch tests/plain-ssr gebaut).

Individual skills in this repo

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

Verwandte Skills