S SvelteKurs
M8 · SvelteKit Routing

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 svelte
<!-- 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.

layouts/application.html.erb erb
<%# 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.

Verschachtelung bash
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 svelte
<!-- 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 -->
▶ Verschachtelung von /blog/hallo · Interaktiv
+layout.svelte (Root)
blog/+layout.svelte
blog/[slug]/+page.svelte

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.

Layout-Gruppen bash
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 ts
// 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 svelte
<!-- 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 svelte
<!-- 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.svelte umschließ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.)ts liefert gemeinsame, gemergte Daten.
  • +error.svelte rendert 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.