Skip to main content
Technology & EngineeringSvelte251 lines

Sveltekit Routing

SvelteKit file-based routing system including layouts, groups, and advanced route patterns

Quick Summary29 lines
You are an expert in SvelteKit's file-based routing system, helping developers structure applications with pages, layouts, route parameters, groups, and navigation.

## Key Points

- **Co-locate related files.** Keep `+page.svelte`, `+page.server.js`, and `+error.svelte` in the same route directory for clear ownership.
- **Use layout groups** to share layouts across unrelated URL paths without nesting them under a common URL prefix.
- **Validate parameters** with matchers instead of runtime checks in load functions.
- **Prefer `<a>` tags over `goto()`** for standard navigation — it enables SvelteKit's client-side router and keeps links accessible.
- **Use shallow routing** (`pushState`/`replaceState` from `$app/navigation`) for modals and tabs that should update the URL without a full navigation.
- **Missing `+layout.svelte` slot.** Forgetting `{@render children()}` (Svelte 5) in a layout causes child pages to not render.
- **Ambiguous routes.** Two sibling dynamic parameters (`[slug]` and `[id]`) at the same level conflict. Use matchers to disambiguate.
- **Trailing slashes.** Configure `trailingSlash` in `svelte.config.js` to avoid duplicate routes and SEO issues.
- **Forgetting `+page.svelte`.** A directory without `+page.svelte` does not produce a route, even if it has a layout or load function.

## Quick Example

```
src/routes/
  docs/[...path]/
    +page.svelte        → /docs/a, /docs/a/b/c
```

```
src/routes/
  [[lang]]/about/
    +page.svelte        → /about, /en/about, /fr/about
```
skilldb get svelte-skills/Sveltekit RoutingFull skill: 251 lines
Paste into your CLAUDE.md or agent config

Routing and Layouts — SvelteKit

You are an expert in SvelteKit's file-based routing system, helping developers structure applications with pages, layouts, route parameters, groups, and navigation.

Core Philosophy

SvelteKit's file-based routing turns the file system into the single source of truth for your application's URL structure. This is a stronger commitment than configuration-based routing: the directory tree is not an approximation of the routes — it is the routes. A new developer can open src/routes/ and immediately understand every URL the application serves, what layout wraps each page, and where error boundaries catch failures. This transparency is the primary design goal.

Co-location is the architectural principle that makes SvelteKit routing powerful. A route directory contains everything that route needs: the page component (+page.svelte), the data loader (+page.server.js or +page.js), the error boundary (+error.svelte), and optionally the form actions. When all of a route's concerns live together, navigating the codebase follows the same mental model as navigating the application. You never need to cross-reference a routes configuration file with a components directory and a separate data-fetching layer.

Layout groups (parenthesized directories) and layout resets (+page@.svelte) provide escape hatches for when the URL hierarchy and the layout hierarchy diverge. A marketing site and a dashboard app might share the same domain but need completely different layouts. Groups let you apply different layouts to different route subtrees without affecting the URL structure. This flexibility means the file system convention never becomes a constraint.

Anti-Patterns

  • Missing {@render children()} in Layouts — creating a +layout.svelte that wraps content but forgets to render the child page, causing all child routes to display a blank page with only the layout chrome.

  • Ambiguous Dynamic Route Siblings — placing [slug]/ and [id]/ at the same directory level without parameter matchers, making it impossible for the router to determine which should match. Use matchers like [id=integer] to disambiguate.

  • Relying on Layout State Resetting Between Siblings — expecting component state in a layout to reset when navigating from /blog/a to /blog/b. Layouts persist across sibling navigations, so any state must be explicitly reset using $effect or key blocks.

  • Using goto() for Standard Navigation — calling goto('/about') in a click handler when a plain <a href="/about"> link would provide the same navigation with better accessibility, prefetching, and progressive enhancement.

  • Directory Without +page.svelte — creating a route directory with a layout and load function but no +page.svelte, expecting it to produce a route. Without the page component, the directory is inert.

Overview

SvelteKit uses a file-system-based router where the directory structure under src/routes defines the application's URL routes. Each route can have a page component, layout, server endpoints, error boundaries, and load functions, all co-located in the route directory.

Core Concepts

Basic Pages

A +page.svelte file inside a route directory renders that route's page.

src/routes/
  +page.svelte          → /
  about/
    +page.svelte        → /about
  blog/
    +page.svelte        → /blog

Route Parameters

Dynamic segments are denoted with square brackets.

src/routes/
  blog/
    [slug]/
      +page.svelte      → /blog/hello-world, /blog/my-post
    [category]/[slug]/
      +page.svelte      → /blog/tech/my-post

Accessed via the params object in load functions or $page.params in components:

<script>
  import { page } from '$app/stores';
  // or in a load function: export function load({ params }) { ... }
</script>

<h1>Post: {$page.params.slug}</h1>

Rest Parameters

Catch-all segments use [...rest]:

src/routes/
  docs/[...path]/
    +page.svelte        → /docs/a, /docs/a/b/c

Optional Parameters

Use double brackets for optional segments:

src/routes/
  [[lang]]/about/
    +page.svelte        → /about, /en/about, /fr/about

Parameter Matchers

Constrain parameters with named matchers:

// src/params/integer.js
export function match(param) {
  return /^\d+$/.test(param);
}
src/routes/
  items/[id=integer]/
    +page.svelte        → /items/42 (matches), /items/abc (does not)

Layouts

A +layout.svelte applies to all child routes. It must render a {@render children()} slot (Svelte 5).

<!-- src/routes/+layout.svelte -->
<script>
  let { children } = $props();
</script>

<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>

<main>
  {@render children()}
</main>

<footer>My App</footer>

Layouts nest automatically — a layout at src/routes/dashboard/+layout.svelte wraps all pages under /dashboard/* and is itself wrapped by the root layout.

Layout Groups

Group routes that share a layout without affecting the URL by using parenthesized directory names:

src/routes/
  (marketing)/
    +layout.svelte      ← shared marketing layout
    pricing/
      +page.svelte      → /pricing
    features/
      +page.svelte      → /features
  (app)/
    +layout.svelte      ← shared app layout (with sidebar)
    dashboard/
      +page.svelte      → /dashboard
    settings/
      +page.svelte      → /settings

Breaking Out of Layouts

Use +page@.svelte or +page@<segment>.svelte to reset to a specific parent layout:

src/routes/
  (app)/
    +layout.svelte
    dashboard/
      +page.svelte           ← uses (app) layout
    dashboard/fullscreen/
      +page@.svelte          ← resets to root layout

Implementation Patterns

Programmatic Navigation

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

  async function handleSubmit() {
    await saveData();
    goto('/dashboard');
  }
</script>

Active Link Highlighting

<script>
  import { page } from '$app/stores';
</script>

<nav>
  {#each links as link}
    <a
      href={link.href}
      class:active={$page.url.pathname === link.href}
    >
      {link.label}
    </a>
  {/each}
</nav>

Error Pages

Co-locate +error.svelte with routes to catch errors at that level:

<!-- src/routes/blog/[slug]/+error.svelte -->
<script>
  import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error?.message}</h1>

API Routes (Server Endpoints)

+server.js files define API endpoints:

// src/routes/api/users/+server.js
import { json } from '@sveltejs/kit';

export async function GET({ url }) {
  const limit = Number(url.searchParams.get('limit') ?? 10);
  const users = await db.getUsers(limit);
  return json(users);
}

export async function POST({ request }) {
  const body = await request.json();
  const user = await db.createUser(body);
  return json(user, { status: 201 });
}

Best Practices

  • Co-locate related files. Keep +page.svelte, +page.server.js, and +error.svelte in the same route directory for clear ownership.
  • Use layout groups to share layouts across unrelated URL paths without nesting them under a common URL prefix.
  • Validate parameters with matchers instead of runtime checks in load functions.
  • Prefer <a> tags over goto() for standard navigation — it enables SvelteKit's client-side router and keeps links accessible.
  • Use shallow routing (pushState/replaceState from $app/navigation) for modals and tabs that should update the URL without a full navigation.

Common Pitfalls

  • Missing +layout.svelte slot. Forgetting {@render children()} (Svelte 5) in a layout causes child pages to not render.
  • Ambiguous routes. Two sibling dynamic parameters ([slug] and [id]) at the same level conflict. Use matchers to disambiguate.
  • Layout reuse across navigations. Layouts persist across sibling page navigations. Component state in a layout is not reset when moving between /blog/a and /blog/b — use $effect or key blocks if reset is needed.
  • Trailing slashes. Configure trailingSlash in svelte.config.js to avoid duplicate routes and SEO issues.
  • Forgetting +page.svelte. A directory without +page.svelte does not produce a route, even if it has a layout or load function.

Install this skill directly: skilldb add svelte-skills

Get CLI access →