Hooks and Middleware
Intercepting every request with hooks.server.js, for cross-cutting concerns like auth and logging.
អាន 3 នាទី
Checking authentication inside every single load function works, but it doesn't scale — forget one route, and you've shipped a hole. SvelteKit's hooks.server.js runs on every server request before it reaches any route, making it the right place for logic that should apply everywhere: authentication, logging, headers, error tracking.
The handle hook
// src/hooks.server.js
export async function handle({ event, resolve }) {
const sessionToken = event.cookies.get('session');
if (sessionToken) {
event.locals.user = await getUserFromSession(sessionToken);
}
const response = await resolve(event);
return response;
}handle is called for every request. event gives you the same request context (cookies, url, params) that load functions and actions receive, and whatever you attach to event.locals here becomes available as locals in every load function and action downstream — this is exactly where the locals.user pattern from earlier lessons comes from.
resolve(event) is what actually renders the matched route; calling it is mandatory unless you intend to short-circuit the response yourself (say, returning a redirect before SvelteKit even looks at routing).
Protecting routes centrally
// src/hooks.server.js
import { redirect } from '@sveltejs/kit';
export async function handle({ event, resolve }) {
const sessionToken = event.cookies.get('session');
event.locals.user = sessionToken ? await getUserFromSession(sessionToken) : null;
if (event.url.pathname.startsWith('/dashboard') && !event.locals.user) {
redirect(303, '/login');
}
return resolve(event);
}Now every route under /dashboard is protected in one place, instead of relying on each route's own +page.server.js remembering to check. This is the "middleware" instinct from other frameworks, expressed as a single function that sees every request.
Composing multiple hooks
Real apps often want several independent concerns — logging, auth, security headers — without nesting them into one unreadable function. sequence from @sveltejs/kit/hooks composes multiple handle functions into one:
// src/hooks.server.js
import { sequence } from '@sveltejs/kit/hooks';
async function logging({ event, resolve }) {
const start = Date.now();
const response = await resolve(event);
console.log(`${event.request.method} ${event.url.pathname} — ${Date.now() - start}ms`);
return response;
}
async function auth({ event, resolve }) {
event.locals.user = await getUserFromSession(event.cookies.get('session'));
return resolve(event);
}
export const handle = sequence(logging, auth);Each function runs in order, and each one's resolve(event) call runs everything after it in the chain — logging wraps auth, which wraps the actual route. This keeps unrelated concerns in separate, independently testable functions instead of one growing handle.
Other hooks worth knowing
hooks.server.js also exports handleFetch (rewrite or redirect outgoing fetch calls made from load functions — useful for calling an internal service by a different URL than the public one) and handleError (customize what gets logged, and what gets shown to the user, when an unexpected error is thrown anywhere in the app):
export function handleError({ error, event }) {
console.error('Unhandled error on', event.url.pathname, error);
return { message: 'Something went wrong. Please try again.' };
}handleError's return value is what reaches the client — deliberately separate from the real error, so you can log full details server-side (stack traces, request context) while showing users a safe, generic message instead of leaking internals.
Hooks are powerful precisely because they run unconditionally — which also makes them the wrong place for anything route-specific. Use them for concerns that genuinely apply to the whole app, and leave per-route logic in load functions and actions where it's easier to trace to a single page.