Sobald du in deiner App mehrere Layout-Ebenen hast — App-Layout, Dashboard-Layout, Sub-Layout — kommt eine Frage auf: Wie kommen Daten von oben nach unten? Antwort: SvelteKit sammelt automatisch alle Layout-Daten ein, mischt sie und gibt das Ergebnis an die Page weiter. Du musst nichts manuell durchreichen. Dieser Artikel erklärt, wie genau dieser Datenfluss funktioniert, was bei data wirklich ankommt und wie du gezielt einzelne Loads neu lädst.

Wie der Datenfluss aussieht

Stell dir eine App mit drei Ebenen vor:

text Beispiel-Struktur
src/routes/
├── +layout.server.ts                      App-Layout (User-Info, Theme)
├── +layout.svelte
└── dashboard/
    ├── +layout.server.ts                  Dashboard-Layout (Side-Nav)
    ├── +layout.svelte
    └── projekte/
        ├── +page.server.ts                Page-Load (Projekt-Liste)
        └── +page.svelte

Wenn der Nutzer /dashboard/projekte aufruft, passiert Folgendes:

  1. SvelteKit ruft alle drei load-Funktionen gleichzeitig auf — App-Layout, Dashboard-Layout, Page.
  2. Jede gibt ein Objekt zurück: z. B. { user, theme }, { sideNav }, { projekte }.
  3. SvelteKit mergt diese Objekte zu einem einzigen data-Objekt: { user, theme, sideNav, projekte }.
  4. Diese gemergte Datenmenge wird an alle beteiligten Komponenten als data-Prop übergeben — Page bekommt sie, Dashboard-Layout bekommt sie, App-Layout bekommt sie.

Das heißt: In der Page-Komponente kannst du auf alles zugreifen, auch auf die Layout-Daten. Du musst keinen parent.user-Pfad bauen — die Felder liegen flach nebeneinander.

Layout-Load: einmal laden, überall verfügbar

Das macht Layout-Loads zur idealen Stelle für App-weite Daten:

ts src/routes/+layout.server.ts
export async function load({ locals }) {
    return {
        user: locals.user,
        theme: locals.theme ?? 'light',
    };
}

user und theme sind jetzt überall in der App verfügbar — in jeder Page-Komponente und in jedem Sub-Layout — über die data-Prop. Du musst sie nicht in jedem +page.server.ts neu laden.

svelte Beliebige Page-Komponente
<script>
    let { data } = $props();
</script>

{#if data.user}
    <p>Hallo, {data.user.name}</p>
{/if}

Klassischer Use Case: Im Root-Layout-Load liest du den User aus dem Session-Cookie. Jede Page weiß damit automatisch, ob jemand eingeloggt ist und wie er heißt — ohne dass du das in jedem Load wieder abfragst.

await parent() — wenn ein Load auf einen anderen wartet

Loads laufen parallel. Das ist gut für Performance, aber manchmal brauchst du Daten aus dem Eltern-Load, bevor du den eigenen ausführen kannst. Etwa: Du willst nur die Projekte des eingeloggten Users holen, also brauchst du erst die User-ID aus dem Layout-Load.

Dafür gibt es await parent():

ts src/routes/dashboard/projekte/+page.server.ts
import { db } from '$lib/server/db';

export async function load({ parent }) {
    const { user } = await parent();
    if (!user) return { projekte: [] };

    const projekte = await db.projekt.findMany({
        where: { userId: user.id },
    });
    return { projekte };
}

await parent() wartet, bis alle Eltern-Loads fertig sind, und gibt dir deren gemergten Output. Wichtig zu wissen: Solange du parent() nicht aufrufst, läuft dein Load parallel zu den Eltern-Loads. Erst der await-Aufruf synchronisiert. Das heißt: Wenn du Eltern-Daten erst spät brauchst, hol sie auch erst spät — das spart Wartezeit.

invalidate und invalidateAll: Loads gezielt neu starten

SvelteKit cached Load-Daten — wenn du auf einer Seite bleibst, wird sie nicht ständig neu geladen. Manchmal willst du das aber: Nach einem Form-Submit etwa sollen die neuen Daten sichtbar sein.

Aus $app/navigation gibt es zwei Helfer:

invalidateAll() lädt alle aktiven Load-Funktionen der aktuellen Route neu.

svelte invalidateAll
<script>
    import { invalidateAll } from '$app/navigation';

    async function refresh() {
        await invalidateAll();
    }
</script>

<button onclick={refresh}>Daten neu laden</button>

invalidate(url) lädt nur Loads neu, die von einer bestimmten URL abhängen. Damit das funktioniert, muss der Load die Abhängigkeit deklariert haben — entweder implizit per event.fetch(url) (das wird automatisch verfolgt) oder explizit mit event.depends(url).

ts Mit depends
export async function load({ depends, fetch }) {
    depends('app:projekte');
    const projekte = await fetch('/api/projekte').then((r) => r.json());
    return { projekte };
}
svelte Gezieltes Invalidieren
<script>
    import { invalidate } from '$app/navigation';

    async function refreshProjekte() {
        await invalidate('app:projekte');
    }
</script>

Damit lädst du nur den Projekt-Load neu, ohne die anderen anzufassen. Praktisch in Apps mit vielen unabhängigen Datenquellen.

Daten an Komponenten unter einer Page weitergeben

Eine Page bekommt Daten als data-Prop. Was, wenn du diese Daten an eine untergeordnete Komponente weitergeben willst, die in der Page eingebunden ist?

Antwort: Wie in jeder normalen Svelte-App — über Props. SvelteKit hat hier nichts Eigenes.

svelte +page.svelte
<script>
    import ProjektListe from '$lib/components/ProjektListe.svelte';
    let { data } = $props();
</script>

<h1>Projekte</h1>
<ProjektListe projekte={data.projekte} />

Wer sehr tiefe Komponenten-Bäume hat und die data nicht durch jede Ebene reichen will, nutzt entweder Sveltes Context-API oder einen $state-Container.

Reaktivität in Layouts: das $page-Store-Pattern

Manche Daten brauchst du in einer Komponente, die nicht direkt mit Layout/Page-Loads verbunden ist — z. B. der aktuelle Routen-Pfad in einem Header. Dafür gibt es das page-Objekt aus $app/state.

svelte Header.svelte
<script>
    import { page } from '$app/state';
</script>

<header>
    <a href="/" class:active={page.url.pathname === '/'}>Home</a>
    <a href="/about" class:active={page.url.pathname === '/about'}>Über</a>

    {#if page.data.user}
        <span>Eingeloggt als {page.data.user.name}</span>
    {/if}
</header>

Das page-Objekt enthält:

  • page.url — die aktuelle URL (mit pathname, searchParams, …).
  • page.params — die dynamischen Pfad-Parameter.
  • page.data — die zusammengeführten Layout- und Page-Daten.
  • page.status und page.error — bei Fehlerseiten gefüllt.

Es ist überall in der App verfügbar, ohne dass du es als Prop weitergeben musst.

TypeScript: LayoutData und PageData

Layout- und Page-Loads haben ihre eigenen Type-Aliase aus ./$types:

ts Typisiertes Layout-Load
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
    return { user: locals.user };
};
svelte Layout-Komponente mit Type
<script lang="ts">
    import type { LayoutData } from './$types';
    let { data, children }: { data: LayoutData; children: any } = $props();
</script>

Wichtig zu verstehen: PageData und LayoutData aus den $types einer Route enthalten bereits die gemergten Daten aus den Eltern-Layouts. Du musst sie nicht selbst zusammenbauen — SvelteKit kennt die Hierarchie und liefert dir den korrekten Gesamttyp.

FAQ — die häufigsten Datenfluss-Fragen

Wo lege ich App-weite Daten ab — Layout-Load oder globaler Store? Wenn die Daten server-seitig geholt werden (User aus Cookie, Theme aus Datenbank), gehört das in den Root-Layout-Load (src/routes/+layout.server.ts). Wenn du etwas reines Client-State hast (z. B. UI-Toggles, Modal-Status), ist ein globaler $state-Container in .svelte.ts der bessere Platz.

Warum sehe ich data.user in der Page, obwohl ich nur ein Layout-Load habe? Weil SvelteKit Layout- und Page-Daten mergt. Was im Layout-Load zurückgegeben wird, ist auch in der Page-Komponente verfügbar — flach, nicht verschachtelt.

Was passiert bei Konflikten zwischen Layout- und Page-Load? Wenn beide ein Feld mit demselben Namen liefern, gewinnt der Page-Load (das tieferliegende). Praktisch: Wenn du einen Default im Layout setzt, kann eine spezifische Page ihn überschreiben.

Reichen Loads für globalen App-State aus? Für server-geholte Daten ja. Für clientseitigen UI-State (offene Modals, Toast-Liste, ungespeichertes Form-Feld) nein — der wird beim Navigieren zurückgesetzt, weil Loads bei Routen-Wechsel neu laufen. Solchen State legst du in $state außerhalb des Load-Kreislaufs ab.

Muss ich await parent() immer rufen, wenn ich Layout-Daten brauche? Nur wenn du sie innerhalb der Load-Funktion brauchst (zum Filtern oder Validieren). Wenn du sie nur in der Komponente nutzen willst, sind sie automatisch in data — kein parent()-Aufruf nötig.

Wie navigiere ich nach erfolgreichem Submit zur Liste und sehe die neuen Daten? Nach dem Submit invalidateAll() rufen, dann goto('/liste'). Bei einer Form Action mit use:enhance macht SvelteKit das automatisch — die Daten werden invalidiert und neu geladen.

Was passiert, wenn der Load eine Exception wirft? Wenn es ein bewusster Fehler ist (mit error(...) oder redirect(...)), zeigt SvelteKit die passende Fehlerseite oder leitet weiter. Eine andere Exception (Datenbank tot, Type-Mismatch) führt zu einer 500-Fehlerseite. In Production siehst du eine generische Meldung, in Development den Stack-Trace.

Kann ich in einer Komponente direkt einen Load ausführen? Nein — Loads sind an Routen gebunden. Wer in einer Komponente serverseitig Daten holen will, baut entweder einen +server.ts-Endpoint und ruft ihn per fetch auf, oder lädt die Daten in dem Load der Route, in der die Komponente lebt.

Weiterführende Ressourcen

Externe Quellen

Verwandte Artikel

/ Weiter

Zurück zu SvelteKit Routing & Loading

Zur Übersicht