or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

server-hooks.mddocs/reference/

Server Hooks

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.

Capabilities

Sequence 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);

Handle Hook

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';
    }
  });
};

HandleFetch Hook

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);
};

HandleServerError Hook

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 })
  };
};

HandleValidationError Hook

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
    }))
  };
};

ServerInit Hook

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');
};

ClientInit Hook

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');
};

HandleClientError Hook

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 Hook

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 Hook

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)
  }
};

Types

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 };

Notes

  • Server hooks are defined in src/hooks.server.ts file
  • Client hooks are defined in src/hooks.client.ts file
  • Universal hooks (reroute, transport) are defined in src/hooks.js file
  • sequence() is imported from @sveltejs/kit/hooks
  • Hook types are imported from @sveltejs/kit
  • The handle hook can return a custom Response or call resolve(event) to continue
  • resolve() options:
    • transformPageChunk: Modify HTML chunks as they're streamed
    • filterSerializedResponseHeaders: Control which headers clients can read
    • preload: Control which resources get preload hints
  • handleFetch intercepts fetch() calls during SSR and load functions
  • handleError has both server and client versions - shapes error objects sent to client
  • handleValidationError processes validation errors from remote functions before they reach the client
  • init has both server and client versions - runs once when context starts
  • reroute changes request paths before routing (universal hook)
  • transport allows custom type serialization across server/client boundary (universal hook)
  • Use event.locals to pass data between server hooks and routes
  • HandleValidationError must return App.Error (not optional)