Wenn du in SvelteKit eine neue Seite anlegen willst, machst du genau eine Sache: Du legst einen Ordner an. Der Ordner-Name wird zur URL, eine Datei +page.svelte darin macht ihn zur sichtbaren Seite. Keine Routing-Konfiguration in einer separaten Datei, keine routes.ts-Liste, keine <Route path="...">-Komponenten. Die Verzeichnis-Struktur ist die Routing-Konfiguration. Dieser Artikel zeigt das Prinzip von der einfachsten Form bis zu fortgeschrittenen Mustern wie dynamischen Parametern, optionalen Segmenten und Layout-Gruppen.
Statische Routen — der Standardfall
Du willst eine Seite /about haben? Leg einen Ordner routes/about/ an und pack eine +page.svelte rein. Fertig. Keine weitere Anmeldung nötig — die Seite ist sofort verfügbar.
Hier ein Überblick mit mehreren Routen auf einmal:
src/routes/ Pfad
├── +page.svelte /
├── about/+page.svelte /about
├── kontakt/+page.svelte /kontakt
├── produkte/
│ ├── +page.svelte /produkte
│ └── neu/+page.svelte /produkte/neu
└── impressum/+page.svelte /impressumDas war’s für statische URLs. Keine Routen-Konfiguration in einer separaten Datei, keine Library — die Verzeichnis-Struktur ist die Konfiguration.
Dynamische Parameter mit [param]
Statische Routen sind okay, solange du jede Seite einzeln pflegst. Sobald du aber viele Seiten mit gleicher Struktur hast — Produktdetails, User-Profile, Blog-Artikel — willst du eine Vorlage, die für alle gilt. Genau das machen dynamische Parameter.
Du gibst einem Ordner einen Namen in eckigen Klammern, und SvelteKit behandelt den Inhalt zur Laufzeit als variabel. Der Klammer-Name (id, slug, …) wird zum Parameter-Namen, den du im Code abrufen kannst.
src/routes/ Pfad
├── blog/
│ ├── +page.svelte /blog
│ └── [slug]/+page.svelte /blog/<slug>
└── users/
├── +page.svelte /users
└── [id]/+page.svelte /users/<id>In der Komponente liest du den dynamischen Wert aus params:
<script>
import { page } from '$app/state';
</script>
<h1>Blogeintrag: {page.params.slug}</h1>Häufiger holt man den Parameter aber direkt in der load-Funktion ab — siehe Artikel zu load-Funktionen.
Mehrere dynamische Segmente
Funktionieren wie erwartet:
src/routes/
└── shops/
└── [shop]/
└── products/
└── [product]/+page.svelteURL /shops/berlin/products/42 setzt params.shop = 'berlin' und params.product = '42'.
Rest-Parameter mit [...rest]
Manchmal weißt du nicht, wie viele URL-Segmente kommen — etwa für eine Doku-Hierarchie oder einen File-Browser. Mit drei Punkten vor dem Namen fängst du alle restlichen Segmente ab:
src/routes/
└── docs/
└── [...path]/+page.svelteURL /docs/svelte/grundlagen/setup setzt params.path = 'svelte/grundlagen/setup'. Die Variable enthält den ganzen Rest als String, mit / als Trenner.
Klassischer Use Case: eine Dokumentationsseite, die ihre Inhalte aus einer Datenbank oder aus Markdown-Dateien zieht und den Pfad als Schlüssel benutzt.
Optionale Parameter mit [[param]]
Doppelte eckige Klammern markieren ein Segment als optional — die Route matcht mit oder ohne diesen Teil.
src/routes/
└── [[lang]]/+page.svelteDiese eine Datei matcht sowohl / als auch /de oder /en. In der Komponente ist params.lang entweder ein String oder undefined.
Klassisch für Multi-Sprache-Sites: Ohne Sprach-Präfix gilt eine Default-Sprache, mit Präfix wird gewechselt.
Parameter-Matcher: Validierung im Routing
Ein dynamischer Parameter matcht standardmäßig alles. Manchmal will man das einschränken — etwa nur Zahlen oder nur bestimmte Werte. Dafür gibt es Matcher.
import type { ParamMatcher } from '@sveltejs/kit';
export const match: ParamMatcher = (param) => /^\d+$/.test(param);src/routes/
└── users/
└── [id=integer]/+page.svelte/users/42 matcht (42 ist eine Zahl). /users/abc matcht nicht — SvelteKit liefert eine 404, ohne dass deine Route überhaupt aufgerufen wird. Das ist sauber, weil du dir Validierung in der Route sparst, und schnell, weil der Match auf Routing-Ebene passiert.
Übliche Matcher-Patterns: [id=integer], [slug=slug], [lang=lang] mit jeweils einer Datei in src/params/. Klare Validierung, klare URL-Struktur.
Layout-Gruppen mit (name)
Stell dir vor, deine App hat zwei sehr unterschiedliche Bereiche: Eine öffentliche Marketing-Seite mit Header und Footer, und einen eingeloggten App-Bereich mit Sidebar und ohne Header. Wie organisierst du das, wenn beide Bereiche auf derselben Domain liegen — /about als Marketing, /dashboard als App?
Eine Möglichkeit wäre, die App-Routen unter /app/dashboard zu verschieben. Aber das ändert die URLs und ist oft nicht gewünscht. Hier kommen Layout-Gruppen ins Spiel.
Mit Klammern um einen Ordnernamen — (marketing) oder (app) — sagst du SvelteKit: „Dieser Ordner gibt nur Layout-Struktur, soll aber nicht in der URL auftauchen.” Du bekommst gemeinsame Layouts, ohne dass die URLs sich ändern.
src/routes/
├── (marketing)/
│ ├── +layout.svelte gemeinsames Marketing-Layout
│ ├── +page.svelte /
│ ├── about/+page.svelte /about
│ └── pricing/+page.svelte /pricing
└── (app)/
├── +layout.svelte gemeinsames App-Layout (Sidebar etc.)
├── dashboard/+page.svelte /dashboard
└── settings/+page.svelte /settingsWichtig: Die Klammer-Ordner erscheinen nicht in der URL. /about bleibt /about, nicht /marketing/about. Sie dienen nur dazu, Routen logisch zu gruppieren und ihnen ein gemeinsames Layout zu geben.
Typischer Anwendungsfall: Eine App hat eine öffentliche Marketing-Seite und einen eingeloggten Bereich. Beide haben sehr unterschiedliche Layouts (Header, Navigation, Footer). Statt jedes +page.svelte selbst um das Layout zu wickeln, gehören sie in zwei Layout-Gruppen.
Programmatische Navigation
Routing per <a href="..."> ist Standard und sollte der Default sein — SvelteKit fängt diese Klicks ab und navigiert clientseitig. Manchmal brauchst du aber JavaScript-Navigation, etwa nach einem erfolgreichen Submit.
<script>
import { goto } from '$app/navigation';
async function save() {
await fetch('/api/save', { method: 'POST' });
goto('/success');
}
</script>
<button onclick={save}>Speichern</button>Weitere Helfer aus $app/navigation:
goto(url)— Programmatische Navigation.invalidate(...)— Bestimmteload-Funktionen neu ausführen, ohne zu navigieren.invalidateAll()— Alleload-Funktionen neu ausführen.preloadData(url)— Schon mal die Daten der Ziel-URL laden, ohne zu navigieren.
Den letzten Punkt nutzt SvelteKit auch automatisch — beim Hover über einen Link kann es schon die Daten der Zielseite laden. Das macht Klicks gefühlt instant.
Routing-Tabelle für die Übersicht
| Ordner-Name | Bedeutung | URL-Beispiel |
|---|---|---|
about | Statisches Segment | /about |
[id] | Dynamischer Parameter (Pflicht) | /users/42 |
[[lang]] | Optionaler Parameter | / oder /de |
[...path] | Rest-Parameter (matcht alle restlichen Segmente) | /docs/a/b/c |
[id=matcher] | Validierter Parameter | nur wenn matcher matcht |
(name) | Layout-Gruppe (nicht in URL) | URL ohne den Ordnernamen |
Interessantes
Ein paar Details zum Routing, die nicht offensichtlich sind, aber das Verständnis vertiefen:
Du brauchst nie ein <Route>-Tag im Code.
In React-Welt ist man gewohnt, eine <Routes>-Komponente irgendwo aufzubauen, mit <Route path="..." element={...}>-Einträgen für jeden Pfad. In SvelteKit existiert so etwas nicht. Die einzigen „Routing-Daten” sind Verzeichnisse und Dateinamen. Wer das einmal verstanden hat, vermisst die Konfig-Datei nie wieder.
Spezifische Routen schlagen dynamische.
Wenn du sowohl routes/users/[id]/+page.svelte als auch routes/users/me/+page.svelte hast, gewinnt bei der URL /users/me immer die spezifische Variante. SvelteKit sortiert die Matches intern: Erst alles mit konkreten Pfad-Segmenten, dann dynamische Parameter, dann Rest-Parameter. So kannst du gezielt eine „Spezial-Seite” innerhalb einer dynamischen Route haben — ohne extra Aufwand.
Layout-Gruppen können beliebig tief verschachtelt werden.
Du kannst innerhalb von (app)/ weitere Gruppen wie (app)/(authed)/ anlegen. Das ergibt Layout-Stapel, die alle übereinander liegen. Bei großen Apps ist das ein eleganter Weg, um z. B. „App-Layout + Authed-Layout + Admin-Layout” als drei Ebenen zu modellieren.
Layout-Gruppen können dieselbe URL haben.
Du kannst (marketing)/+page.svelte und (app)/(loggedout)/+page.svelte beide als URL / definieren — solange sie sich nicht überschneiden. Praktisch ist das selten, aber es zeigt, dass die Klammer-Ordner wirklich nur „kosmetische Gruppierung” sind, die SvelteKit ignoriert, sobald es um die URL geht.
[id=integer] matcht zur Routing-Zeit.
Mit einem Parameter-Matcher kannst du nicht-passende URLs auf 404 weiterleiten, ohne dass deine eigentliche Route überhaupt aufgerufen wird. Das ist nicht nur eleganter, sondern auch schneller — keine load-Funktion läuft, keine Datenbank-Anfrage geht raus. Ein toller Schutz vor Bot-Anfragen, die zufällige Pfade probieren.
Trailing Slash ist konfigurierbar.
Standardmäßig ist /about und /about/ für SvelteKit dieselbe Route. Du kannst aber per kit.trailingSlash in svelte.config.js festlegen, dass nur eine Variante gilt. Konsistenz ist hier wichtig für SEO — Suchmaschinen sehen / und kein-/ als zwei verschiedene URLs.
Statische Dateien gewinnen über Routen.
Wenn du eine Datei static/api.json hast und eine Route routes/api/+page.svelte, beantwortet SvelteKit die URL /api.json mit der statischen Datei und /api mit der Route. Beide existieren also parallel — solange die Pfade nicht exakt kollidieren, funktioniert es. Bei tatsächlichen Kollisionen gewinnt die statische Datei.