Run code on every server request with hooks.server.ts — read cookies, attach the user to event.locals, and guard routes.
Why: hooks.server.ts runs on every request before your routes. The handle function lets you inspect the request and decide what happens — it’s the central place for auth, logging, and headers. Call resolve(event) to continue to the normal route.
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
// ...do something with the request here
const response = await resolve(event)
return response
}Why: event.locals is a per-request bag you fill in the hook and read later in any load or action. Read a session cookie once here, look up the user, and store it — no repeating that logic everywhere.
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit'
import { getUser } from '$lib/server/auth'
export const handle: Handle = async ({ event, resolve }) => {
const session = event.cookies.get('session')
event.locals.user = session ? await getUser(session) : null
return resolve(event)
}Why: anything you put on locals in the hook is available in every server load and action via the event. So gate a page by reading locals.user — no need to re-check the cookie.
// src/routes/dashboard/+page.server.ts
import { redirect } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ locals }) => {
if (!locals.user) redirect(303, '/login')
return { user: locals.user }
}Why: TypeScript needs to know what’s on locals. Declare it once in src/app.d.ts and every event.locals across the app is typed. Note: this special file describes app-wide types to SvelteKit.
// src/app.d.ts
declare global {
namespace App {
interface Locals {
user: { id: string; name: string } | null
}
}
}
export {}