4 min read
On this page

File-Based Routing

Overview

SvelteKit uses the filesystem to define routes. Every file inside src/routes maps directly to a URL in your application. There is no router configuration file, no array of route objects, no manual path matching. The directory structure is the URL structure.

This design eliminates an entire category of bugs where routes and components fall out of sync. When you look at the src/routes folder, you see your entire application's URL hierarchy at a glance.

The Core Files

SvelteKit recognizes specific filenames that serve different purposes at each route level.

src/routes/
  +page.svelte        → renders at /
  +page.ts            → loads data for the page
  +layout.svelte      → wraps all child pages
  +error.svelte       → displays when errors occur
  about/
    +page.svelte      → renders at /about
  blog/
    +page.svelte      → renders at /blog
    [slug]/
      +page.svelte    → renders at /blog/some-post

+page.svelte

Every route needs a +page.svelte file. This is the component that renders when a user visits that URL.

<!-- src/routes/about/+page.svelte -->
<h1>About Us</h1>
<p>We build tools for developers.</p>

Visiting /about renders this component. No registration, no import, no configuration.

+page.ts & +page.server.ts

These files export a load function that fetches data before the page renders.

// src/routes/blog/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/database';

export const load: PageServerLoad = async () => {
  const posts = await db.post.findMany({
    orderBy: { createdAt: 'desc' }
  });
  return { posts };
};
<!-- src/routes/blog/+page.svelte -->
<script lang="ts">
  let { data } = $props();
</script>

<h1>Blog</h1>
{#each data.posts as post}
  <article>
    <a href="/blog/{post.slug}">{post.title}</a>
  </article>
{/each}

+layout.svelte

Layouts wrap child pages. A layout at src/routes/+layout.svelte wraps every page in the application.

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { type Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();
</script>

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

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

<footer>Copyright 2026</footer>

Every page in the application now has the nav and footer. Child layouts add more structure on top.

+error.svelte

When a load function throws an error or a page is not found, SvelteKit renders the nearest +error.svelte file.

<!-- src/routes/+error.svelte -->
<script lang="ts">
  import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error?.message}</h1>
<a href="/">Go back home</a>

Nested Layouts

Layouts compose by nesting. A layout in a subdirectory wraps only the pages in that subtree.

src/routes/
  +layout.svelte            → wraps everything
  dashboard/
    +layout.svelte          → wraps only /dashboard/*
    +page.svelte            → /dashboard
    settings/
      +page.svelte          → /dashboard/settings
    analytics/
      +page.svelte          → /dashboard/analytics
<!-- src/routes/dashboard/+layout.svelte -->
<script lang="ts">
  import { type Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();
</script>

<div class="dashboard-container">
  <aside>
    <a href="/dashboard">Overview</a>
    <a href="/dashboard/settings">Settings</a>
    <a href="/dashboard/analytics">Analytics</a>
  </aside>
  <section>
    {@render children()}
  </section>
</div>

When a user visits /dashboard/settings, SvelteKit renders the root layout, then the dashboard layout inside it, then the settings page inside that. Each layer adds its own structure.

Route Groups

Sometimes you want multiple pages to share a layout without that layout affecting the URL. Route groups use parentheses to create layout boundaries that do not add URL segments.

src/routes/
  (marketing)/
    +layout.svelte          → marketing layout (no URL impact)
    +page.svelte            → renders at /
    about/
      +page.svelte          → renders at /about
    pricing/
      +page.svelte          → renders at /pricing
  (app)/
    +layout.svelte          → app layout (no URL impact)
    dashboard/
      +page.svelte          → renders at /dashboard
    settings/
      +page.svelte          → renders at /settings

The marketing pages get a public-facing layout with a hero section and footer. The app pages get a sidebar layout with navigation. The URLs remain clean: /about, /dashboard, not /(marketing)/about.

<!-- src/routes/(marketing)/+layout.svelte -->
<script lang="ts">
  import { type Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();
</script>

<header class="marketing-header">
  <a href="/">Brand</a>
  <a href="/pricing">Pricing</a>
  <a href="/about">About</a>
</header>

{@render children()}

<footer class="marketing-footer">
  <p>Get started today.</p>
</footer>
<!-- src/routes/(app)/+layout.svelte -->
<script lang="ts">
  import { type Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();
</script>

<div class="app-shell">
  <nav class="sidebar">
    <a href="/dashboard">Dashboard</a>
    <a href="/settings">Settings</a>
  </nav>
  <main>
    {@render children()}
  </main>
</div>

How File-Based Routing Eliminates Configuration

In traditional routers, you define routes separately from the components they render.

Traditional approach (React Router, Vue Router, etc.):
  1. Create a component file
  2. Import it into a route config
  3. Define the path, component, loader, and error boundary
  4. If you rename a file, update the route config
  5. If you forget to update the config, the route breaks silently

SvelteKit approach:
  1. Create a file in src/routes
  2. Done.

There is no route configuration that can fall out of sync with your components. Renaming a folder renames the route. Deleting a folder removes the route. The filesystem is the single source of truth.

The Mental Model: Filesystem = URL Structure

The way to think about SvelteKit routing is that src/routes is a mirror of your URL space. Every directory is a path segment. Every +page.svelte is a page. Every +layout.svelte is a wrapper.

Filesystem:                          URL:
src/routes/+page.svelte              /
src/routes/about/+page.svelte        /about
src/routes/blog/+page.svelte         /blog
src/routes/blog/[slug]/+page.svelte  /blog/:slug
src/routes/docs/intro/+page.svelte   /docs/intro

When you add a new feature, you create a folder. When you restructure your navigation, you move folders. When you review a PR that changes routing, the diff shows exactly which URLs changed because the file paths are the URLs.

Common Pitfalls

  • Forgetting +page.svelte: A directory without +page.svelte does not create a route. If you have src/routes/about/ with only a layout file, visiting /about returns a 404.
  • Confusing +layout.svelte scope: A layout applies to every child route in its directory and all subdirectories. Placing a heavy dashboard layout at the root level wraps your marketing pages too.
  • Route group naming collisions: Two route groups cannot both define the same URL path. If (marketing)/about/+page.svelte and (app)/about/+page.svelte both exist, SvelteKit cannot resolve /about.
  • Missing the root layout: If you have no src/routes/+layout.svelte, SvelteKit uses a default that just renders children. Any global elements like nav or footer need to go in an explicit root layout.
  • Putting components in src/routes: Only +page.svelte, +layout.svelte, +error.svelte, +server.ts, and their data-loading counterparts are recognized. Shared components belong in src/lib.

Key Takeaways

  • SvelteKit maps src/routes directly to URLs. The filesystem is the router. There is no separate configuration to maintain.
  • +page.svelte renders a page, +layout.svelte wraps child pages, and +error.svelte handles errors. These are the three core building blocks.
  • Nested layouts compose automatically. A layout wraps everything in its directory and below.
  • Route groups with (parentheses) create layout boundaries without affecting URLs. Use them to give different sections of your app different layouts.
  • The mental model is simple: if you can navigate the filesystem, you can understand the routing. What you see in src/routes is what the user sees in the URL bar.