# SvelteKit Routing & Data Loading Guide ## Overview SvelteKit is the official framework for building Svelte applications. It provides file-based routing, server-side rendering, and powerful data loading primitives. --- ## 📁 File-Based Routing ### Route Structure ``` src/routes/ ├── +page.svelte # → / ├── +layout.svelte # Layout for all routes ├── about/ │ └── +page.svelte # → /about ├── blog/ │ ├── +page.svelte # → /blog │ └── [slug]/ │ └── +page.svelte # → /blog/:slug ├── users/ │ ├── +page.svelte # → /users │ ├── [id]/ │ │ ├── +page.svelte # → /users/:id │ │ └── +page.server.ts # Server-only load │ └── [...rest]/ │ └── +page.svelte # → /users/* (catch-all) └── api/ └── posts/ └── +server.ts # → /api/posts (API endpoint) ``` ### Route Files Explained | File | Purpose | Runs On | |------|---------|---------| | `+page.svelte` | Page component | Client | | `+page.ts` | Load data for page | Client & Server | | `+page.server.ts` | Load data (server only) | Server only | | `+layout.svelte` | Shared layout | Client | | `+layout.ts` | Load data for layout | Client & Server | | `+layout.server.ts` | Load layout data (server) | Server only | | `+server.ts` | API endpoint | Server only | | `+error.svelte` | Error page | Client | --- ## 📥 Data Loading ### Basic Load Function (+page.ts) ```typescript // src/routes/posts/+page.ts import type { PageLoad } from './$types'; export const load: PageLoad = async ({ fetch }) => { const response = await fetch('/api/posts'); const posts = await response.json(); return { posts }; }; ``` ```svelte

Posts

{#each data.posts as post}

{post.title}

{post.excerpt}

{/each} ``` ### Server-Only Load (+page.server.ts) ```typescript // src/routes/dashboard/+page.server.ts import type { PageServerLoad } from './$types'; import { db } from '$lib/server/database'; import { error, redirect } from '@sveltejs/kit'; export const load: PageServerLoad = async ({ locals, cookies }) => { // Access server-only resources const session = cookies.get('session'); if (!session) { redirect(303, '/login'); } const user = await db.user.findUnique({ where: { sessionId: session } }); if (!user) { error(401, 'Unauthorized'); } const stats = await db.stats.findMany({ where: { userId: user.id } }); return { user: { name: user.name, email: user.email }, stats }; }; ``` ### Load with Parameters ```typescript // src/routes/blog/[slug]/+page.ts import type { PageLoad } from './$types'; import { error } from '@sveltejs/kit'; export const load: PageLoad = async ({ params, fetch }) => { const response = await fetch(`/api/posts/${params.slug}`); if (!response.ok) { error(404, 'Post not found'); } const post = await response.json(); return { post }; }; ``` ### Parent Data Access ```typescript // src/routes/+layout.ts import type { LayoutLoad } from './$types'; export const load: LayoutLoad = async ({ fetch }) => { const response = await fetch('/api/user'); const user = response.ok ? await response.json() : null; return { user }; }; // src/routes/dashboard/+page.ts import type { PageLoad } from './$types'; export const load: PageLoad = async ({ parent }) => { // Access data from parent layout const { user } = await parent(); if (!user) { // Handle unauthenticated state } return { // Additional data }; }; ``` ### Parallel Data Loading ```typescript // src/routes/dashboard/+page.ts import type { PageLoad } from './$types'; export const load: PageLoad = async ({ fetch }) => { // Load data in parallel const [postsRes, statsRes, notificationsRes] = await Promise.all([ fetch('/api/posts'), fetch('/api/stats'), fetch('/api/notifications') ]); return { posts: postsRes.json(), stats: statsRes.json(), notifications: notificationsRes.json() }; }; ``` --- ## 📤 Form Actions ### Basic Form Action ```typescript // src/routes/login/+page.server.ts import type { Actions, PageServerLoad } from './$types'; import { fail, redirect } from '@sveltejs/kit'; import { db } from '$lib/server/database'; export const load: PageServerLoad = async ({ locals }) => { if (locals.user) { redirect(303, '/dashboard'); } }; export const actions: Actions = { default: async ({ request, cookies }) => { const data = await request.formData(); const email = data.get('email')?.toString(); const password = data.get('password')?.toString(); if (!email || !password) { return fail(400, { email, error: 'Email and password are required' }); } const user = await db.user.findUnique({ where: { email } }); if (!user || !await verifyPassword(password, user.passwordHash)) { return fail(401, { email, error: 'Invalid credentials' }); } const session = await createSession(user.id); cookies.set('session', session.id, { path: '/', httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 * 24 * 7 // 1 week }); redirect(303, '/dashboard'); } }; ``` ```svelte
{ loading = true; return async ({ update }) => { loading = false; await update(); }; }} > {#if form?.error}

{form.error}

{/if}
``` ### Named Actions ```typescript // src/routes/settings/+page.server.ts import type { Actions } from './$types'; export const actions: Actions = { updateProfile: async ({ request }) => { const data = await request.formData(); // Handle profile update return { success: true }; }, changePassword: async ({ request }) => { const data = await request.formData(); // Handle password change return { success: true }; }, deleteAccount: async ({ request, cookies }) => { // Handle account deletion cookies.delete('session', { path: '/' }); redirect(303, '/'); } }; ``` ```svelte
``` --- ## 🔌 API Routes (+server.ts) ### RESTful API Endpoint ```typescript // src/routes/api/posts/+server.ts import type { RequestHandler } from './$types'; import { json, error } from '@sveltejs/kit'; import { db } from '$lib/server/database'; // GET /api/posts export const GET: RequestHandler = async ({ url }) => { const limit = Number(url.searchParams.get('limit')) || 10; const offset = Number(url.searchParams.get('offset')) || 0; const posts = await db.post.findMany({ take: limit, skip: offset, orderBy: { createdAt: 'desc' } }); return json(posts); }; // POST /api/posts export const POST: RequestHandler = async ({ request, locals }) => { if (!locals.user) { error(401, 'Unauthorized'); } const body = await request.json(); const post = await db.post.create({ data: { title: body.title, content: body.content, authorId: locals.user.id } }); return json(post, { status: 201 }); }; // src/routes/api/posts/[id]/+server.ts import type { RequestHandler } from './$types'; import { json, error } from '@sveltejs/kit'; // GET /api/posts/:id export const GET: RequestHandler = async ({ params }) => { const post = await db.post.findUnique({ where: { id: params.id } }); if (!post) { error(404, 'Post not found'); } return json(post); }; // PUT /api/posts/:id export const PUT: RequestHandler = async ({ params, request, locals }) => { const post = await db.post.findUnique({ where: { id: params.id } }); if (!post) { error(404, 'Post not found'); } if (post.authorId !== locals.user?.id) { error(403, 'Forbidden'); } const body = await request.json(); const updated = await db.post.update({ where: { id: params.id }, data: body }); return json(updated); }; // DELETE /api/posts/:id export const DELETE: RequestHandler = async ({ params, locals }) => { const post = await db.post.findUnique({ where: { id: params.id } }); if (!post) { error(404, 'Post not found'); } if (post.authorId !== locals.user?.id) { error(403, 'Forbidden'); } await db.post.delete({ where: { id: params.id } }); return new Response(null, { status: 204 }); }; ``` --- ## 🏗️ Layouts ### Root Layout ```svelte
{@render children()}
``` ### Nested Layouts ```svelte
{@render children()}
``` ### Layout Groups ``` src/routes/ ├── (auth)/ # Group - no route segment │ ├── +layout.svelte # Shared layout for auth pages │ ├── login/ │ │ └── +page.svelte │ └── register/ │ └── +page.svelte ├── (app)/ # Group - no route segment │ ├── +layout.svelte # Shared layout for app pages │ ├── dashboard/ │ │ └── +page.svelte │ └── settings/ │ └── +page.svelte └── +layout.svelte # Root layout ``` ### Breaking Out of Layout ```svelte ``` --- ## 🔀 Navigation ### Programmatic Navigation ```svelte

Current path: {$page.url.pathname}

Query: {$page.url.searchParams.get('q')}

``` ### Link Prefetching ```svelte About Posts External Dashboard ``` --- ## ⚠️ Error Handling ### Error Pages ```svelte

{$page.status}

{$page.error?.message}

Go home

Blog Post Not Found

{$page.error?.message}

View all posts
``` ### Throwing Errors ```typescript // In load functions or actions import { error, redirect } from '@sveltejs/kit'; export const load = async ({ params }) => { const post = await getPost(params.id); if (!post) { error(404, { message: 'Post not found', code: 'POST_NOT_FOUND' }); } if (post.draft) { redirect(303, '/blog'); } return { post }; }; ``` --- ## 🔧 Hooks ### Server Hooks (hooks.server.ts) ```typescript // src/hooks.server.ts import type { Handle, HandleFetch, HandleServerError } from '@sveltejs/kit'; // Runs for every request export const handle: Handle = async ({ event, resolve }) => { // Authentication const session = event.cookies.get('session'); if (session) { event.locals.user = await getUserFromSession(session); } // Performance timing const start = Date.now(); const response = await resolve(event, { // Transform HTML transformPageChunk: ({ html }) => html.replace('%theme%', 'dark') }); // Log request duration console.log(`${event.request.method} ${event.url.pathname} - ${Date.now() - start}ms`); return response; }; // Customize server-side fetch export const handleFetch: HandleFetch = async ({ request, fetch }) => { // Add auth header to internal API calls if (request.url.startsWith('https://api.internal.com')) { request.headers.set('Authorization', `Bearer ${API_KEY}`); } return fetch(request); }; // Handle unexpected errors export const handleError: HandleServerError = async ({ error, event }) => { console.error('Server error:', error); // Report to error tracking service await reportError(error, event); return { message: 'An unexpected error occurred', code: 'INTERNAL_ERROR' }; }; ``` ### Client Hooks (hooks.client.ts) ```typescript // src/hooks.client.ts import type { HandleClientError } from '@sveltejs/kit'; export const handleError: HandleClientError = async ({ error, event }) => { console.error('Client error:', error); // Report to error tracking await reportClientError(error); return { message: 'Something went wrong', code: 'CLIENT_ERROR' }; }; ``` --- ## 📋 Best Practices ### ✅ Do: - Use `+page.server.ts` for sensitive data - Return typed data from load functions - Use `use:enhance` for progressive enhancement - Handle loading and error states - Use route groups for layout organization - Validate form data server-side ### ❌ Don't: - Put secrets in `+page.ts` (runs on client) - Forget to await `parent()` in child loads - Block navigation with slow loads - Ignore accessibility in forms - Hardcode URLs (use `$page` store)