Introduction
After 17 days of building with Svelte, you've got components down—now it's time to structure how users navigate your app. SvelteKit routing: a practical Svelte & SvelteKit guide will walk you through the file-based routing system that turns your folder structure into live URLs without you writing a single route config. By the end of this guide, you'll understand how to scale from a simple marketing site to a complex multi-section application using pages, dynamic params, layouts, and route groups.
File-based routing is a mental shift if you're coming from Express or manual routing frameworks. Once it clicks, you'll appreciate how much boilerplate it removes and how naturally your folder hierarchy mirrors your URL structure.
Pages: from folders to URLs
The foundation of SvelteKit routing is dead simple: any +page.svelte file becomes a route. Your file structure is your router.
src/routes/
├── +page.svelte → /
├── about/
│ └── +page.svelte → /about
├── blog/
│ └── +page.svelte → /blog
└── contact/
└── +page.svelte → /contactThat's it. No route declarations, no middleware chains. Visiting /about loads src/routes/about/+page.svelte. Visiting / loads src/routes/+page.svelte (your homepage). The naming convention is strict: +page.svelte, not index.svelte.
Here's a real scenario: you're building a SaaS dashboard. Your routes might look like:
src/routes/
├── +page.svelte → / (landing)
├── app/
│ └── +page.svelte → /app (dashboard home)
├── pricing/
│ └── +page.svelte → /pricing
├── docs/
│ └── +page.svelte → /docs
└── legal/
└── +page.svelte → /legalEach page component receives data through load functions (defined in +page.js or +page.server.js), and can define server-side logic without extra ceremony. The structure naturally separates concerns: everything under app/ is authenticated, everything under pricing/ is public.
Dynamic params: capturing values in URLs
Static routes get you far, but most apps need to capture dynamic segments. A blog needs /blog/my-first-post, an e-commerce site needs /products/widget-123. SvelteKit uses bracket notation: [param] folders capture URL segments.
src/routes/blog/
├── +page.svelte → /blog (list all posts)
└── [slug]/
└── +page.svelte → /blog/my-first-post
(slug = "my-first-post")Your page component accesses the param via the data prop, which comes from a load function. Here's a practical blog example:
// src/routes/blog/[slug]/+page.server.js
export async function load({ params }) {
const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json());
if (!post) {
throw error(404, 'Post not found');
}
return { post };
}
// src/routes/blog/[slug]/+page.svelte
<script>
export let data;
</script>
<h1>{data.post.title}</h1>
<p>{data.post.content}</p>You can also use rest parameters to match multiple segments. If you're building nested documentation with paths like /docs/setup/installation and /docs/api/auth/token:
src/routes/docs/
└── [...breadcrumb]/
└── +page.svelte → /docs/setup/installation
(breadcrumb = ["setup", "installation"])Access it the same way: params.breadcrumb is an array of segments. This is powerful for wikis, help centers, and docs sites where depth varies.
One critical detail: params are URL-decoded automatically, so /products/widget%20deluxe arrives as params.id === "widget deluxe". Always encode safely when building links.
Layouts: shared structure across routes
Most apps need repeated UI—navbars, sidebars, footers—that wrap multiple pages. That's where +layout.svelte comes in. Any +layout.svelte wraps all +page.svelte files in its directory and subdirectories.
src/routes/
├── +layout.svelte → wraps all routes
├── +page.svelte → /
├── app/
│ ├── +layout.svelte → wraps /app and below
│ ├── +page.svelte → /app
│ └── dashboard/
│ └── +page.svelte → /app/dashboardThe root layout typically includes your global nav and footer. Child layouts can add more structure. Here's a practical example from an e-commerce app:
// src/routes/+layout.svelte (global)
<script>
import Navigation from '$lib/Navigation.svelte';
import Footer from '$lib/Footer.svelte';
</script>
<Navigation />
<main>
<slot />
</main>
<Footer />
// src/routes/shop/+layout.svelte (shop-specific)
<script>
import Sidebar from '$lib/ShopSidebar.svelte';
</script>
<div class="shop-wrapper">
<Sidebar />
<section>
<slot />
</section>
</div>The global layout renders, then the slot loads the shop layout, then that slot loads the page. Nesting is natural and automatic. You also get +layout.js for shared data—load user info once at the root, access it in every page without re-fetching.
Route groups: organizing without changing URLs
Layouts are great, but sometimes you want different structures for different sections. Your marketing site needs one layout (big hero, call-to-action), your authenticated dashboard needs another (sidebar, user menu). Route groups solve this.
Wrap a folder in parentheses—(marketing) or (app)—and its segments are not included in the URL.
src/routes/
├── (marketing)/
│ ├── +layout.svelte → marketing layout
│ ├── +page.svelte → /
│ ├── about/
│ │ └── +page.svelte → /about
│ └── pricing/
│ └── +page.svelte → /pricing
├── (app)/
│ ├── +layout.svelte → dashboard layout
│ ├── +page.svelte → /app
│ ├── dashboard/
│ │ └── +page.svelte → /app/dashboard
│ └── settings/
│ └── +page.svelte → /app/settingsNow / uses the marketing layout, but /app uses the dashboard layout. Same URL, different structure. This is how you scale: group related routes, apply different layouts, apply different middleware (like auth checks), all without polluting your URLs.
A real-world case: you're building a SaaS platform. Non-authenticated users see /, /pricing, /docs under a public layout. Once logged in, they're redirected to /dashboard, /settings, /team under a protected layout. Two groups, two layouts, crystal-clear separation:
// src/routes/(marketing)/+layout.server.js
export async function load({ locals }) {
if (locals.user) {
redirect(307, '/dashboard');
}
}
// src/routes/(app)/+layout.server.js
export async function load({ locals }) {
if (!locals.user) {
redirect(307, '/');
}
return { user: locals.user };
}Each group enforces its own invariants. No repetition, no conditional hell.
Common mistakes
Using wrong file names: It's +page.svelte, not index.svelte or page.svelte. The + is non-negotiable.
Forgetting brackets for dynamics: A file at src/routes/user/profile.svelte won't capture a user ID. You need src/routes/user/[id]/+page.svelte.
Nesting layouts too deep: Every +layout.svelte adds rendering overhead. Use them strategically, not in every folder. Reserve them for sections that genuinely share UI.
Assuming route groups are hidden: They're hidden from the URL, but not from your build. The folder still exists; you're just controlling the URL shape. Group logic still runs—use them to organize, not to hide.
Not handling 404s in dynamic routes: If /blog/nonexistent hits your [slug] route, throw an error with error(404, 'Not found'). Silently returning undefined breaks your page.
Key takeaways
+page.sveltefiles define routes; your folder structure mirrors your URL structure.[param]folders capture dynamic segments;[...rest]captures multiple.+layout.sveltewraps pages in its directory and below, sharing UI and data.- Route groups
(name)organize without changing URLs, letting you apply different layouts to different sections. - Combine all four to build large, navigable apps that scale from a simple landing page to a complex multi-tenant platform.






Comments
Loading comments…