← スキル一覧に戻る

sveltekit-server-patterns
by maxnoller
Personal collection of Claude Code plugins
⭐ 0🍴 0📅 2026年1月17日
SKILL.md
name: SvelteKit Server Patterns description: 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.
SvelteKit Server Patterns
SvelteKit provides multiple server-side patterns for data loading, mutations, and request handling.
Load Functions
Page Server Load (+page.server.ts)
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
};
};
Layout Server Load (+layout.server.ts)
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
};
};
Universal Load (+page.ts)
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 |
Parallel Loading
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 };
};
Using Parent Data
// 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 };
};
Form Actions
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');
}
};
Named Actions
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>
Progressive Enhancement
<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>
API Routes (+server.ts)
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 });
};
Dynamic API Routes
// 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 });
};
Hooks (hooks.server.ts)
handle - Request Middleware
// 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;
};
Sequence Multiple Handles
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);
handleFetch - Modify Server Fetches
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);
};
handleError - Error Handling
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().
Locals Type Safety
// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: { id: string; email: string } | null;
}
interface PageData {
user?: { id: string; email: string };
}
}
}
export {};
Common Antipatterns
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>
スコア
総合スコア
50/100
リポジトリの品質指標に基づく評価
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
レビュー
💬
レビュー機能は近日公開予定です