08 - Hooks

What are Hooks?

Hooks are functions that run on every request, letting you add global behavior — authentication, logging, error handling, etc. They live in special files at the src/ root.

File Runs on Purpose
src/hooks.server.ts Server Auth, logging, modify requests/responses
src/hooks.client.ts Client Client-side error handling

Server Hooks

handle — Runs on Every Request

The main hook. It receives every request and must call resolve(event) to continue processing. You can modify the request before and the response after:

// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  // BEFORE: runs before the route handler
  console.log(`Request: ${event.url.pathname}`);
  
  // Add data to event.locals — available in load functions and actions
  event.locals.user = await getUser(event.cookies);
  
  const response = await resolve(event);  // process the route
  
  // AFTER: runs after the route handler
  response.headers.set('X-Custom-Header', 'value');
  
  return response;
};

event.locals is a per-request object you can attach data to. It's available in all load functions and form actions for that request.

handleFetch — Modify Server-Side Fetch

Intercept fetch() calls made in load functions during SSR:

// src/hooks.server.ts
import type { HandleFetch } from '@sveltejs/kit';

export const handleFetch: HandleFetch = async ({ request, fetch }) => {
  // Add auth headers to API calls made during SSR
  if (request.url.startsWith('https://api.example.com')) {
    request.headers.set('Authorization', 'Bearer token');
  }
  
  return fetch(request);
};

handleError — Handle Unexpected Errors

Catches unhandled errors on the server. Use for logging — the returned object becomes $page.error:

// src/hooks.server.ts
import type { HandleServerError } from '@sveltejs/kit';

export const handleError: HandleServerError = async ({ error, event }) => {
  console.error('Server error:', error);
  
  return {
    message: 'Something went wrong'  // shown to the user via +error.svelte
  };
};

Client Hooks

handleError — Client-Side Errors

Same concept as server, but for errors that happen in the browser:

// src/hooks.client.ts
import type { HandleClientError } from '@sveltejs/kit';

export const handleError: HandleClientError = async ({ error }) => {
  console.error('Client error:', error);
  
  return {
    message: 'An error occurred'
  };
};

Authentication Example

A common pattern — check for a session cookie, load the user, and protect routes:

// src/hooks.server.ts
import { redirect } from '@sveltejs/kit';
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  // 1. Check session cookie
  const session = event.cookies.get('session');
  
  // 2. Load user if session exists
  if (session) {
    event.locals.user = await db.getUserBySession(session);
  }
  
  // 3. Protect routes — redirect to login if not authenticated
  if (event.url.pathname.startsWith('/dashboard')) {
    if (!event.locals.user) {
      redirect(303, '/login');
    }
  }
  
  return resolve(event);
};

Then access locals.user in any load function or action:

// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';

export const load: LayoutServerLoad = async ({ locals }) => {
  return { user: locals.user };  // available in all pages
};

Sequencing Multiple Hooks

Use sequence() to compose multiple handle functions — they run in order:

// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import type { Handle } from '@sveltejs/kit';

const auth: Handle = async ({ event, resolve }) => {
  event.locals.user = await getUser(event);
  return resolve(event);
};

const logger: Handle = async ({ event, resolve }) => {
  console.log(event.url.pathname);
  return resolve(event);
};

export const handle = sequence(auth, logger);  // auth runs first, then logger

Transform HTML

Modify the rendered HTML before sending it to the client:

// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  const response = await resolve(event, {
    transformPageChunk: ({ html }) => {
      return html.replace('%lang%', 'en');  // replace placeholder in app.html
    }
  });
  
  return response;
};

Key Points

  • handle is the main hook — runs on every request, use for auth and middleware
  • event.locals passes data from hooks → load functions → actions
  • handleFetch intercepts SSR fetch calls (add auth headers, rewrite URLs)
  • handleError catches unhandled errors for logging
  • sequence() composes multiple handle functions in order
  • Hooks are global — they affect every route in the app