Routing
SvelteKit routing: file-based routes, layouts, params, load functions, and server vs client routing.
Svelte — SvelteKit routing
EXAMPLE
// ===== File-based routes =====
// src/routes/
// +page.svelte /
// about/
// +page.svelte /about
// blog/
// +page.svelte /blog
// [slug]/
// +page.svelte /blog/:slug
// (group)/
// +page.svelte / (group hides from URL)
// ===== Layouts =====
// src/routes/+layout.svelte
<script>
let { children } = $props(); // Svelte 5 runes
</script>
<header>nav</header>
<main>{@render children()}</main>
<footer>...</footer>
// Nested layouts:
// src/routes/blog/+layout.svelte -> wraps blog/* only
// ===== Dynamic params =====
// src/routes/blog/[slug]/+page.svelte
<script>
import { page } from '$app/stores';
$: slug = $page.params.slug;
</script>
// In load function:
// src/routes/blog/[slug]/+page.ts
export async function load({ params, fetch }) {
const r = await fetch(\`/api/posts/${params.slug}\`);
return { post: await r.json() };
}
// In page:
<script>
let { data } = $props();
</script>
<h1>{data.post.title}</h1>
// ===== load: server vs universal =====
// +page.ts runs on server during SSR, then in the browser
// +page.server.ts runs only on the server (DB calls, secrets)
// src/routes/dashboard/+page.server.ts
export async function load({ locals }) {
if (!locals.user) throw redirect(303, '/login');
return { user: locals.user };
}
// ===== Navigation =====
import { goto } from '$app/navigation';
goto('/about');
// Or <a href="/about">About</a> (SPA navigation by default)
// ===== Form actions =====
// src/routes/login/+page.server.ts
export const actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const email = data.get('email');
// ...
cookies.set('session', token, { path: '/' });
throw redirect(303, '/dashboard');
},
};
// src/routes/login/+page.svelte
<form method="POST">
<input name="email" />
<button>Sign in</button>
</form>
// ===== API routes =====
// src/routes/api/posts/[id]/+server.ts
import { json } from '@sveltejs/kit';
export async function GET({ params }) {
return json(await db.posts.findById(params.id));
}
export async function POST({ request }) {
const body = await request.json();
return json(await db.posts.create(body), { status: 201 });
}
// ===== Hooks =====
// src/hooks.server.ts
export async function handle({ event, resolve }) {
event.locals.user = await getUserFromCookie(event.cookies);
return resolve(event);
}
// ===== Adapters (target hosting) =====
// adapter-auto (default), adapter-node, adapter-vercel, adapter-cloudflare,
// adapter-static (SPA), adapter-netlify
// ===== Patterns to internalise =====
// - +page.server.ts for DB / secrets; +page.ts for universal data
// - Layouts for shared chrome; nested layouts for sub-sections
// - hooks.server.ts to set locals on every request (auth, request id)
// - Form actions over fetch + setState for traditional forms
// ===== Pitfalls =====
// - Calling DB code in +page.ts (runs on the client too)
// - Forgetting throw redirect / throw error
// - Mutating data on the page in place; SvelteKit expects load to return the shape
// - One adapter chosen, then features used that the target does not support
Why it matters
SvelteKit routing is file-based: +page.svelte for routes, +layout.svelte for chrome, +page.server.ts for server-only data, [param] for dynamics, form actions for traditional forms, hooks for cross-cutting. Pick an adapter early; the API stays the same as the target changes.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// src/routes/+page.svelte = '/' // src/routes/users/+page.svelte = '/users' // src/routes/users/[id]/+page.svelte = '/users/:id'Try it Yourself »
Discussion
Loading…