S SvelteKurs
M9 · Laden & Mutieren

load — universal (+page.ts)

Daten laden, bevor die Seite gerendert wird. Die universelle load-Funktion läuft auf Server UND Client — ideal für öffentliche APIs.

Wozu eine load-Funktion?

Eine Komponente soll nicht selbst im onMount Daten nachladen — das führt zu leeren Seiten, Flackern und schlechtem SEO. In SvelteKit legst du das Laden in eine load-Funktion. Sie läuft, bevor die Seite gerendert wird; ihr Rückgabewert steht in der Komponente als data-Prop bereit.

Die universelle Variante lebt in einer Datei namens +page.ts (neben deiner +page.svelte). „Universell" heißt: Sie läuft beim ersten Aufruf auf dem Server (für HTML + SEO) und danach bei clientseitiger Navigation direkt im Browser.

src/routes/blog/+page.ts ts
// src/routes/blog/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ fetch }) => {
  const res = await fetch('/api/posts');
  const posts = await res.json();
  return { posts }; // landet in der data-Prop
};

Und so kommst du in der Komponente an die Daten — über die data-Prop:

src/routes/blog/+page.svelte svelte
<script lang="ts">
  import type { PageData } from './$types';
  let { data }: { data: PageData } = $props();
</script>

<h1>Blog</h1>
<ul>
  {#each data.posts as post (post.id)}
    <li>{post.title}</li>
  {/each}
</ul>

🛤️ Rails-Vergleich: load ≈ Controller-Action

Eine load-Funktion ist das SvelteKit-Gegenstück zu einer Controller-Action, die @variablen setzt. Was du in Rails @posts = Post.all nennst und im View benutzt, ist hier return { posts } und in der Komponente data.posts. Der Unterschied: universelles load läuft auch im Browser weiter, nicht nur pro Request.

Rails ruby
# Rails: der Controller lädt Daten und legt sie in @-Variablen ab
class PostsController < ApplicationController
  def index
    @posts = Post.all          # -> im View als @posts verfügbar
  end

  def show
    @post = Post.find(params[:slug])
  end
end

Das eingebaute fetch

Beachte, dass fetch aus dem load-Argument kommt — nutze dieses und nicht das globale fetch. SvelteKits Version kann relative URLs auflösen, sendet bei internen Aufrufen Cookies und Header mit und vermeidet auf dem Server einen echten Netzwerk-Roundtrip zu eigenen +server.ts-Endpunkten.

💡 Immer das load-fetch verwenden

Schreib async ({ fetch }) => { ... } und benutze genau dieses fetch. So funktioniert dein Code identisch auf Server und Client.

Zugriff auf Routen-Parameter

Dynamische Segmente wie [slug] stehen im load-Argument unter params bereit — analog zu params[:slug] in Rails.

src/routes/blog/[slug]/+page.ts ts
// src/routes/blog/[slug]/+page.ts
import type { PageLoad } from './$types';

export const load: PageLoad = async ({ params, fetch }) => {
  // params.slug kommt aus dem Ordnernamen [slug]
  const res = await fetch(`/api/posts/${params.slug}`);
  return { post: await res.json() };
};

Universal oder Server?

Die Faustregel für die Wahl der load-Datei:

  • +page.ts (universal) — wenn die Daten aus einer öffentlichen API kommen, kein Geheimnis nötig ist und du beim Navigieren keinen Server-Roundtrip brauchst. Kann auch nicht-serialisierbare Werte (z. B. Klassen-Instanzen, Funktionen) an die Seite geben.
  • +page.server.ts (server-only) — sobald du direkt auf die Datenbank, Secrets, Umgebungsvariablen oder locals (z. B. den eingeloggten User) zugreifst. Der Code landet nie im Browser-Bundle.

⚠️ Faustregel

Im Zweifel: Braucht der Code ein Geheimnis oder DB-Zugriff? → +page.server.ts. Sonst reicht +page.ts. Beide Dateien dürfen sogar gleichzeitig existieren — die Ergebnisse werden zusammengeführt.

Zusammenfassung

💡 Das Wichtigste

  • +page.ts exportiert load — läuft auf Server und Client.
  • Der Rückgabewert landet als data-Prop in der +page.svelte.
  • Immer das fetch aus dem Argument verwenden; params liefert Routen-Segmente.
  • Secrets/DB → lieber +page.server.ts (nächste Lektion).

🎯 Probier es selbst

Lege eine Route /lernen/daten/demo mit einer +page.ts an, die von https://jsonplaceholder.typicode.com/users lädt und die Namen in einer Liste anzeigt. Nutze das eingebaute fetch und gib das Ergebnis als data.users zurück.