From harness-claude
Implement SvelteKit's hooks.server.ts to intercept requests, populate locals with user sessions, add security headers/CORS/logging, short-circuit auth, compose handlers via sequence, and modify responses.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Intercept every request, populate locals, modify responses, and handle errors using SvelteKit's hooks.server.ts
Implements SvelteKit load functions for pre-render data fetching: server-only (+page.server.ts), universal (+page.ts), streaming, layouts, error handling, and invalidation.
Builds full-stack SvelteKit web apps with file-based routing, SSR, SSG, API routes, form actions, and load functions. Activates for +page.svelte, +layout.svelte, or Svelte full-stack queries.
Enforces Svelte 5 best practices in SvelteKit: runes ($state, $derived, $effect), $props(), $bindable(), load functions, form actions, and SSR patterns to fix outdated Svelte 4 code.
Share bugs, ideas, or general feedback.
Intercept every request, populate locals, modify responses, and handle errors using SvelteKit's hooks.server.ts
handle functions using sequencehandle — the main request hook:
handle function from src/hooks.server.ts. It intercepts every request and must call resolve(event) to continue:// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// Runs before load functions and routes
const response = await resolve(event);
// Runs after
return response;
};
event.locals — accessible in all load functions and actions:export const handle: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get('session_id');
if (sessionId) {
event.locals.user = await db.session.findUser(sessionId);
}
return resolve(event);
};
// src/app.d.ts — declare the locals shape
declare global {
namespace App {
interface Locals {
user: User | null;
}
}
}
export {};
export const handle: Handle = async ({ event, resolve }) => {
if (event.url.pathname.startsWith('/api') && !event.locals.user) {
return new Response('Unauthorized', { status: 401 });
}
return resolve(event);
};
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
return response;
};
transformPageChunk to modify HTML output (inject scripts, replace placeholders):const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('%theme%', event.locals.theme ?? 'light'),
});
sequence — composing multiple handle functions:
sequence from @sveltejs/kit/hooks to chain multiple handle functions cleanly:import { sequence } from '@sveltejs/kit/hooks';
import { auth } from './hooks/auth';
import { logging } from './hooks/logging';
import { security } from './hooks/security';
export const handle = sequence(auth, logging, security);
Each function in the sequence calls resolve(event) to pass to the next.
handleFetch — modifying server-side fetch calls:
fetch calls made inside load functions (only on the server during SSR):import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ request, fetch }) => {
// Rewrite internal API calls to avoid the network round-trip
if (request.url.startsWith('https://myapp.com/api/')) {
const url = request.url.replace('https://myapp.com', 'http://localhost:3000');
return fetch(new Request(url, request));
}
return fetch(request);
};
handleError — unhandled server errors:
$page.error contains:import type { HandleServerError } from '@sveltejs/kit';
export const handleError: HandleServerError = async ({ error, event, status, message }) => {
const errorId = crypto.randomUUID();
// Log to your error tracking service
await reportError({ error, errorId, url: event.url.pathname });
// Return safe error info (never expose internals)
return {
message: 'An unexpected error occurred',
errorId,
};
};
Client hooks (hooks.client.ts):
handleError from src/hooks.client.ts to capture client-side errors:// src/hooks.client.ts
import type { HandleClientError } from '@sveltejs/kit';
export const handleError: HandleClientError = ({ error, event }) => {
Sentry.captureException(error);
return { message: 'Something went wrong' };
};
Execution order:
Request
→ handle (hooks.server.ts)
→ layout load (+layout.server.ts)
→ page load (+page.server.ts)
→ page renders
← response
← response
← handle (can modify)
Response
locals type safety:
Always declare your App.Locals interface in src/app.d.ts. TypeScript will then infer the correct types when you access event.locals in load functions and actions.
Auth pattern:
// hooks/auth.ts
import type { Handle } from '@sveltejs/kit';
export const auth: Handle = async ({ event, resolve }) => {
const token = event.cookies.get('auth_token');
event.locals.user = token ? await verifyToken(token) : null;
return resolve(event);
};
// Any load function:
export const load: PageServerLoad = ({ locals }) => {
if (!locals.user) redirect(303, '/login');
return { user: locals.user };
};
CORS for API routes:
export const handle: Handle = async ({ event, resolve }) => {
if (event.request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
const response = await resolve(event);
response.headers.set('Access-Control-Allow-Origin', '*');
return response;
};
handleFetch use cases:
https://kit.svelte.dev/docs/hooks