Server hooks intercept requests and allow you to modify responses, add authentication, handle errors, and implement middleware patterns. The hooks module also provides utilities for sequencing multiple hooks.
Chain multiple handle functions together in middleware-like manner.
/**
* Sequences multiple handle functions to run in order
* Properly merges transformPageChunk, preload, and filterSerializedResponseHeaders options
* @param handlers - Handle functions to sequence
* @returns Combined handle function
*/
function sequence(...handlers: Handle[]): Handle;Usage Example:
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import type { Handle } from '@sveltejs/kit';
const authentication: Handle = async ({ event, resolve }) => {
const token = event.cookies.get('session');
if (token) {
event.locals.user = await validateToken(token);
}
return resolve(event);
};
const logging: Handle = async ({ event, resolve }) => {
const start = Date.now();
const response = await resolve(event);
const duration = Date.now() - start;
console.log(`${event.request.method} ${event.url.pathname} - ${duration}ms`);
return response;
};
const security: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
return response;
};
// Combine hooks - they run in order
export const handle = sequence(authentication, logging, security);Main server hook that intercepts all requests.
/**
* Server hook that handles all requests
* @param input - Object containing event and resolve function
* @returns Response or Promise that resolves to Response
*/
type Handle = (input: {
event: RequestEvent;
resolve: Resolve;
}) => Response | Promise<Response>;Usage Examples:
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
// Basic authentication
export const handle: Handle = async ({ event, resolve }) => {
if (event.url.pathname.startsWith('/admin')) {
if (!event.locals.user?.isAdmin) {
return new Response('Unauthorized', { status: 401 });
}
}
return resolve(event);
};
// Modify HTML before sending
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event, {
transformPageChunk: ({ html }) => {
return html.replace('%analytics%', getAnalyticsScript());
}
});
return response;
};
// Control preloading
export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
preload: ({ type }) => {
// Only preload JS, not CSS
return type === 'js';
}
});
};
// Filter serialized headers
export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
filterSerializedResponseHeaders: (name) => {
// Only allow specific headers to be read by client
return name === 'x-custom-header';
}
});
};Intercept fetch requests made during server-side rendering.
/**
* Hook that intercepts fetch calls during SSR
* @param input - Object with event, request, and fetch function
* @returns Response or Promise that resolves to Response
*/
type HandleFetch = (input: {
event: RequestEvent;
request: Request;
fetch: typeof fetch;
}) => Response | Promise<Response>;Usage Example:
// src/hooks.server.ts
import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
// Add authentication to internal API calls
if (request.url.startsWith('https://api.internal.com')) {
request.headers.set('Authorization', `Bearer ${event.locals.apiToken}`);
}
// Rewrite external URLs to internal during SSR
if (request.url.startsWith('https://external-api.com')) {
const url = new URL(request.url);
url.host = 'internal-api.local';
request = new Request(url, request);
}
return fetch(request);
};Handle errors that occur during server-side rendering.
/**
* Hook that handles server-side errors
* @param input - Object with error, event, status, and message
* @returns Optional custom error object (App.Error), void, or Promise
*/
type HandleServerError = (input: {
error: unknown;
event: RequestEvent;
status: number;
message: string;
}) => MaybePromise<void | App.Error>;Usage Example:
// src/hooks.server.ts
import type { HandleServerError } from '@sveltejs/kit';
export const handleError: HandleServerError = ({ error, event, status, message }) => {
// Log error
console.error('Server error:', {
error,
path: event.url.pathname,
status,
message
});
// Send to error tracking service
if (status === 500) {
trackError(error, {
user: event.locals.user?.id,
url: event.url.href
});
}
// Return custom error shape for client
return {
message: 'An error occurred',
code: status,
// Don't expose sensitive error details to client
...(process.env.NODE_ENV === 'development' && { details: message })
};
};Handle validation errors from remote functions.
/**
* Hook that handles validation errors from remote functions
* @param input - Object with issues array and event
* @returns App.Error object that will be sent to client (REQUIRED - return value is not optional)
* @since 2.47.3
*/
type HandleValidationError<Issue extends StandardSchemaV1.Issue = StandardSchemaV1.Issue> = (input: {
issues: Issue[];
event: RequestEvent;
}) => MaybePromise<App.Error>;Usage Example:
// src/hooks.server.ts
import type { HandleValidationError } from '@sveltejs/kit';
export const handleValidationError: HandleValidationError = ({ issues, event }) => {
// Log validation errors
console.log('Validation failed:', issues);
// Return custom App.Error shape (must return, not optional)
return {
message: 'Validation failed',
validationErrors: issues.map(issue => ({
field: issue.path?.join('.'),
error: issue.message
}))
};
};Initialize server context before handling requests.
/**
* Hook that runs once when server starts
* @returns void or Promise<void>
* @since 2.10.0
*/
type ServerInit = () => MaybePromise<void>;Usage Example:
// src/hooks.server.ts
import type { ServerInit } from '@sveltejs/kit';
export const init: ServerInit = async () => {
// Initialize database connection
await initDatabase();
// Warm up caches
await preloadCache();
console.log('Server initialized');
};Initialize client context when the app starts in the browser.
/**
* Hook that runs once when app starts in browser
* @returns void or Promise<void>
* @since 2.10.0
*/
type ClientInit = () => MaybePromise<void>;Usage Example:
// src/hooks.client.ts
import type { ClientInit } from '@sveltejs/kit';
export const init: ClientInit = async () => {
// Initialize client-side services
await initAnalytics();
// Set up error tracking
setupErrorTracking();
console.log('Client initialized');
};Handle errors that occur during client-side navigation.
/**
* Hook that handles client-side errors
* @param input - Object with error, event, status, and message
* @returns Optional custom error object (App.Error), void, or Promise
*/
type HandleClientError = (input: {
error: unknown;
event: NavigationEvent;
status: number;
message: string;
}) => MaybePromise<void | App.Error>;Usage Example:
// src/hooks.client.ts
import type { HandleClientError } from '@sveltejs/kit';
export const handleError: HandleClientError = ({ error, event, status, message }) => {
// Log error to console
console.error('Client error:', error);
// Send to error tracking
trackClientError(error, {
url: event.url?.href,
status
});
// Return custom error shape
return {
message: 'Something went wrong',
code: status
};
};Reroute requests to different paths based on URL patterns.
/**
* Hook that reroutes requests to different paths
* @param event - Object with url and fetch
* @returns New path string, void to keep original path, or Promise
* @since 2.3.0
*/
type Reroute = (event: { url: URL; fetch: typeof fetch }) => MaybePromise<void | string>;Usage Example:
// src/hooks.js (universal hook - works on both server and client)
import type { Reroute } from '@sveltejs/kit';
export const reroute: Reroute = ({ url }) => {
// Redirect old blog URLs to new structure
if (url.pathname.startsWith('/blog/post/')) {
return url.pathname.replace('/blog/post/', '/articles/');
}
// Support locale in URL path
if (url.pathname.startsWith('/en/')) {
return url.pathname.slice(3);
}
};Transport custom types across the server/client boundary.
/**
* Hook that transports custom types across server/client boundary
* Each transporter has encode/decode pair for serialization
* @since 2.11.0
*/
type Transport = Record<string, Transporter>;
interface Transporter<T = any, U = any> {
/** Encode value on server - return false if not this type, otherwise return encoding */
encode: (value: T) => false | U;
/** Decode encoding on client back into original type */
decode: (data: U) => T;
}Usage Example:
// src/hooks.js (universal hook)
import type { Transport } from '@sveltejs/kit';
class CustomDate {
constructor(public timestamp: number) {}
toDate() {
return new Date(this.timestamp);
}
}
export const transport: Transport = {
CustomDate: {
encode: (value) => {
// Check if value is CustomDate instance
if (value instanceof CustomDate) {
// Return encoding (must be truthy, not false)
return { timestamp: value.timestamp };
}
return false;
},
decode: (data) => {
// Reconstruct CustomDate from encoding
return new CustomDate(data.timestamp);
}
},
// Another example with Map
Map: {
encode: (value) => value instanceof Map && Array.from(value.entries()),
decode: (entries) => new Map(entries)
}
};type Resolve = (
event: RequestEvent,
opts?: ResolveOptions
) => Response | Promise<Response>;
interface ResolveOptions {
transformPageChunk?: (input: {
html: string;
done: boolean;
}) => string;
filterSerializedResponseHeaders?: (
name: string,
value: string
) => boolean;
preload?: (input: {
type: 'font' | 'css' | 'js' | 'asset';
path: string;
}) => boolean;
}
interface ValidationError {
issues: ValidationIssue[];
}
interface ValidationIssue {
path: (string | number)[];
message: string;
}
type ActionResult<
Success extends Record<string, any> | undefined = Record<string, any>,
Failure extends Record<string, any> | undefined = Record<string, any>
> =
| { type: 'success'; status: number; data?: Success }
| { type: 'failure'; status: number; data?: Failure }
| { type: 'redirect'; status: number; location: string }
| { type: 'error'; status?: number; error: any };src/hooks.server.ts filesrc/hooks.client.ts filesrc/hooks.js filesequence() is imported from @sveltejs/kit/hooks@sveltejs/kithandle hook can return a custom Response or call resolve(event) to continueresolve() options:
transformPageChunk: Modify HTML chunks as they're streamedfilterSerializedResponseHeaders: Control which headers clients can readpreload: Control which resources get preload hintshandleFetch intercepts fetch() calls during SSR and load functionshandleError has both server and client versions - shapes error objects sent to clienthandleValidationError processes validation errors from remote functions before they reach the clientinit has both server and client versions - runs once when context startsreroute changes request paths before routing (universal hook)transport allows custom type serialization across server/client boundary (universal hook)event.locals to pass data between server hooks and routesHandleValidationError must return App.Error (not optional)