Hooks

Hooks are SvelteKit’s middleware layer: server-side functions that run for every request (or every unexpected error), defined in src/hooks.server.js (or .ts).

The handle hook

handle wraps every request. You call resolve() to get the response, and can rewrite it on the way out:

/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
    const response = await resolve(event, {
        transformPageChunk: ({ html }) => html.replace('%lang%', 'en'),
        filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
        preload: ({ type }) => type === 'js' || type === 'css',
    });
    response.headers.set('x-request-id', crypto.randomUUID());
    return response;
}
  • event carries the URL, params, cookies, locals, and getClientAddress()
  • transformPageChunk rewrites HTML as it streams (chunked, so stateful transforms need care)
  • filterSerializedResponseHeaders controls which headers survive into load-function fetches
  • resolve() never throws; errors thrown inside handle itself are fatal

Use sequence() from @sveltejs/kit/hooks to compose multiple handle functions (auth, then logging, then request-id).

handleError

Runs on unexpected errors, both for SSR and in remote functions:

/** @type {import('@sveltejs/kit').HandleServerError} */
export function handleError({ event, status, message, error }) {
    log.error(event.route.id, error);
    return { message: 'Something went wrong' };
}

Remote functions add a validation flavor: when a remote function receives invalid input, the hook is called with kind: 'validation' and the validation issues:

/** @type {import('@sveltejs/kit').HandleServerError} */
export function handleError({ kind, issues }) {
    if (kind === 'validation') {
        console.error(issues);
        return { message: 'Invalid input' };
    }
}

Gotcha: whatever handleError returns is shown to the client. Never echo raw error messages or validation issue details you would not publish.

What hooks are not

They are not the place for route logic (that belongs in +page.server.js / remote functions), and they do not run for prerendered pages served statically. Cache headers, auth redirects, and A/B flags are the classic residents; see Prerendering and Caching for the cache-header case.