iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…