# 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
```
### 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
```
### 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)