Server-side SvelteKit patterns including load functions, form actions, hooks, and API routes. Use when working with +page.server.ts, +layout.server.ts, +server.ts, hooks.server.ts, load functions, form actions, handle/handleFetch/handleError hooks, or when users ask about server-side data loading, authentication, authorization, or API endpoints in SvelteKit.
Implements server-side SvelteKit patterns including load functions, form actions, hooks, and API routes.
npx claudepluginhub maxnoller/claude-code-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
examples/hooks.server.tsexamples/page.server.tsSvelteKit provides multiple server-side patterns for data loading, mutations, and request handling.
Server-only data loading—code never reaches the client:
// src/routes/dashboard/+page.server.ts
import type { PageServerLoad } from './$types';
import { db } from '$lib/server/db';
import { error } from '@sveltejs/kit';
export const load: PageServerLoad = async ({ params, locals }) => {
if (!locals.user) {
error(401, 'Unauthorized');
}
const stats = await db.stats.findUnique({
where: { userId: locals.user.id }
});
return {
stats,
user: locals.user
};
};
Share data across child routes:
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return {
user: locals.user
};
};
Runs on both server and client—avoid secrets:
// src/routes/posts/+page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch }) => {
// Use SvelteKit's fetch for cookie forwarding
const posts = await fetch('/api/posts').then(r => r.json());
return { posts };
};
When to use which:
| Use case | File |
|---|---|
| Database queries | +page.server.ts |
| API keys/secrets | +page.server.ts |
| Shared auth state | +layout.server.ts |
| Client-side caching | +page.ts |
| Public API calls | +page.ts |
export const load: PageServerLoad = async ({ fetch }) => {
// DON'T: Sequential (slow)
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());
// DO: Parallel (fast)
const [user, posts] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json())
]);
return { user, posts };
};
// src/routes/dashboard/settings/+page.server.ts
export const load: PageServerLoad = async ({ parent }) => {
// Avoid waterfall - fetch your data first
const settings = await db.settings.find();
// Then get parent data
const { user } = await parent();
return { settings, user };
};
Handle form submissions with progressive enhancement:
// src/routes/login/+page.server.ts
import type { Actions, PageServerLoad } from './$types';
import { fail, redirect } from '@sveltejs/kit';
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') as string;
const password = data.get('password') as string;
// Validate
if (!email) {
return fail(400, { email, missing: true });
}
// Authenticate
const user = await authenticate(email, password);
if (!user) {
return fail(400, { email, incorrect: true });
}
// Set session
cookies.set('session', user.sessionId, { path: '/' });
redirect(303, '/dashboard');
}
};
export const actions: Actions = {
login: async ({ request }) => { /* ... */ },
register: async ({ request }) => { /* ... */ },
logout: async ({ cookies }) => {
cookies.delete('session', { path: '/' });
redirect(303, '/');
}
};
<form method="POST" action="?/login">
<!-- login form -->
</form>
<form method="POST" action="?/register">
<!-- register form -->
</form>
<form method="POST" action="?/logout">
<button>Logout</button>
</form>
<script>
import { enhance } from '$app/forms';
let loading = $state(false);
</script>
<form
method="POST"
use:enhance={() => {
loading = true;
return async ({ update }) => {
await update();
loading = false;
};
}}
>
<button disabled={loading}>
{loading ? 'Submitting...' : 'Submit'}
</button>
</form>
Create REST API endpoints:
// src/routes/api/posts/+server.ts
import type { RequestHandler } from './$types';
import { json, error } from '@sveltejs/kit';
export const GET: RequestHandler = async ({ url, locals }) => {
const limit = Number(url.searchParams.get('limit')) || 10;
const posts = await db.posts.findMany({ take: limit });
return json(posts);
};
export const POST: RequestHandler = async ({ request, locals }) => {
if (!locals.user) {
error(401, 'Unauthorized');
}
const { title, content } = await request.json();
const post = await db.posts.create({
data: { title, content, authorId: locals.user.id }
});
return json(post, { status: 201 });
};
// src/routes/api/posts/[id]/+server.ts
export const GET: RequestHandler = async ({ params }) => {
const post = await db.posts.findUnique({
where: { id: params.id }
});
if (!post) error(404, 'Post not found');
return json(post);
};
export const DELETE: RequestHandler = async ({ params, locals }) => {
if (!locals.user) error(401);
await db.posts.delete({ where: { id: params.id } });
return new Response(null, { status: 204 });
};
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// Before route handling
const session = event.cookies.get('session');
if (session) {
event.locals.user = await getUserFromSession(session);
}
// Resolve the request
const response = await resolve(event);
// After route handling
response.headers.set('X-Custom-Header', 'value');
return response;
};
import { sequence } from '@sveltejs/kit/hooks';
const auth: Handle = async ({ event, resolve }) => {
// Auth logic
return resolve(event);
};
const logger: Handle = async ({ event, resolve }) => {
console.log(`${event.request.method} ${event.url.pathname}`);
return resolve(event);
};
export const handle = sequence(auth, logger);
export const handleFetch: HandleFetch = async ({ request, fetch }) => {
// Add auth to internal API calls
if (request.url.startsWith('https://api.internal/')) {
request = new Request(request, {
headers: {
...Object.fromEntries(request.headers),
'Authorization': `Bearer ${API_KEY}`
}
});
}
return fetch(request);
};
export const handleError: HandleServerError = async ({ error, event }) => {
const id = crypto.randomUUID();
// Log to error tracking service
console.error(`Error ${id}:`, error);
// Return safe message to client
return {
message: 'An error occurred',
id
};
};
Note: handleError only catches unexpected errors, not errors thrown with error().
// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: { id: string; email: string } | null;
}
interface PageData {
user?: { id: string; email: string };
}
}
}
export {};
Don't use load for authorization:
// WRONG: Load runs on client too
export const load = async ({ locals }) => {
if (!locals.user) redirect(303, '/login');
};
// CORRECT: Use hooks for auth
export const handle: Handle = async ({ event, resolve }) => {
if (event.url.pathname.startsWith('/admin')) {
if (!event.locals.user?.isAdmin) {
return new Response('Forbidden', { status: 403 });
}
}
return resolve(event);
};
Don't return excess data:
// WRONG: Returns entire user record
return { user: await db.users.findUnique({ where: { id } }) };
// CORRECT: Return only needed fields
const user = await db.users.findUnique({ where: { id } });
return { user: { id: user.id, name: user.name } };
Don't forget error boundaries:
<!-- src/routes/+error.svelte -->
<script>
import { page } from '$app/stores';
</script>
<h1>{$page.status}</h1>
<p>{$page.error?.message}</p>
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.