Layouts
Ein Layout umschließt eine oder mehrere Seiten mit gemeinsamem Rahmen — Navigation, Footer, Sidebar. In SvelteKit ist das eine +layout.svelte mit einem children-Snippet.
Das Root-Layout
Eine +layout.svelte im Ordner src/routes umschließt jede Seite der App. Sie bekommt über $props ein children-Snippet, das du mit {@render children()} genau dort
renderst, wo die aktuelle Seite erscheinen soll.
<!-- src/routes/+layout.svelte -->
<script lang="ts">
let { children } = $props();
</script>
<header>
<nav><a href="/">Home</a> · <a href="/blog">Blog</a></nav>
</header>
<main>
{@render children()} <!-- hier landet die aktuelle Seite -->
</main>
<footer>© 2026 Mein Projekt</footer>Header und Footer stehen einmal im Layout und erscheinen automatisch auf allen Seiten — ohne
dass eine +page.svelte etwas davon wissen muss.
🛤️ Rails-Vergleich: application.html.erb & content_for
Das Root-Layout entspricht app/views/layouts/application.html.erb. Wo Rails <%= yield %> setzt, rendert SvelteKit {@render children()}.
Verschachtelte Layouts ersetzen das, wofür du in Rails mehrere Layout-Dateien oder content_for bräuchtest.
<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
<body>
<header><%= render "shared/nav" %></header>
<main><%= yield %></main> <%# hier landet die View %>
</body>
</html>
<%# Namespace-Layout: app/views/layouts/admin.html.erb %>Verschachtelte & vererbte Layouts
Legst du eine +layout.svelte in einen Unterordner, wird sie zusätzlich zum
Root-Layout wirksam — für diesen Ordner und alle darunterliegenden Routen. Layouts verschachteln sich, sie ersetzen sich nicht.
src/routes/
├── +layout.svelte # Root-Layout: umschließt ALLES
├── +page.svelte # /
└── blog/
├── +layout.svelte # zusätzlicher Rahmen NUR für /blog/*
├── +page.svelte # /blog
└── [slug]/
└── +page.svelte # /blog/... erbt beide Layouts<!-- src/routes/blog/+layout.svelte -->
<script lang="ts">
let { children } = $props();
</script>
<div class="blog-layout">
<aside>Blog-Sidebar mit Kategorien</aside>
<div class="content">
{@render children()}
</div>
</div>
<!-- Ergebnis für /blog/hallo:
Root-Layout ▸ Blog-Layout ▸ Seite -->Jedes children-Snippet rendert die jeweils tiefere Ebene.
Layout-Gruppen mit (klammern)
Manchmal willst du Routen unterschiedlich einrahmen, ohne ein zusätzliches URL-Segment einzuführen — etwa einen eingeloggten Bereich vs. Marketing-Seiten. Ein Ordnername in runden Klammern bildet eine Gruppe: Er beeinflusst, welches Layout greift, taucht aber nicht in der URL auf.
src/routes/
├── (app)/ # Gruppe — KEIN URL-Segment!
│ ├── +layout.svelte # Layout mit Login-Navigation
│ ├── dashboard/+page.svelte → /dashboard
│ └── profil/+page.svelte → /profil
└── (marketing)/
├── +layout.svelte # anderes Layout (Landingpage-Optik)
└── +page.svelte → /ℹ️ Klammer-Ordner sind unsichtbar
(app)/dashboard/+page.svelte liegt unter der URL /dashboard — die
Gruppe (app) ist nur ein Organisationsmittel, um allen Seiten darin ein
gemeinsames +layout.svelte zu geben.
Layout-Daten
Ein Layout kann Daten laden, die dann auf allen darunterliegenden Seiten verfügbar sind —
ideal für Dinge wie den eingeloggten Nutzer. Dafür gibt es +layout.ts (universal)
bzw. +layout.server.ts (nur Server).
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
// Läuft für JEDE Seite unter diesem Layout.
return {
user: locals.user ?? null
};
};<!-- src/routes/+layout.svelte -->
<script lang="ts">
let { children, data } = $props();
// data.user kommt aus +layout.server.ts — auch in
// untergeordneten +page-Komponenten verfügbar (gemerged).
</script>
{#if data.user}
<p>Angemeldet als {data.user.name}</p>
{/if}
{@render children()}💡 Daten werden zusammengeführt
Die Rückgabe von Layout-load-Funktionen wird mit den Page-Daten gemerged. Eine +page.svelte tief im Baum kann also data.user aus dem Root-Layout mitnutzen, ohne es erneut zu laden.
Fehlerseiten mit +error.svelte
Wirft eine load-Funktion einen Fehler (oder eine Seite existiert nicht), rendert
SvelteKit die nächstgelegene +error.svelte innerhalb der Layouts dieses Zweigs.
So bleibt die umgebende Navigation erhalten, während nur der Inhaltsbereich den Fehler zeigt.
<!-- src/routes/blog/+error.svelte -->
<script lang="ts">
import { page } from '$app/state';
</script>
<h1>{page.status}</h1>
<p>{page.error?.message ?? 'Etwas ist schiefgelaufen.'}</p>Status und Fehlerobjekt liest du reaktiv über page aus $app/state.
Zusammenfassung
💡 Das Wichtigste
+layout.svelteumschließt Seiten;{@render children()}ist das yield.- Layouts verschachteln sich vom Root nach unten und werden vererbt.
- Gruppen
(name)teilen Layouts, ohne die URL zu verändern. +layout.(server.)tsliefert gemeinsame, gemergte Daten.+error.svelterendert Fehler innerhalb des umgebenden Layouts.
🎯 Probier es selbst
Baue ein Root-+layout.svelte mit einer Navigationsleiste und einem Footer. Lege
darunter eine Gruppe (app)/ mit eigenem Layout an, das eine Seitenleiste
ergänzt, und darin die Seiten dashboard und profil. Prüfe, dass
beide unter /dashboard bzw. /profil erreichbar sind — die Gruppe
also nicht in der URL auftaucht.