SvelteKit Hooks
Hook 是你声明的应用级函数。SvelteKit 会在特定事件发生时调用它们,让你细致控制框架行为。
有三种 Hook 文件,它们都是可选的:
src/hooks.server.js:服务端 Hook。src/hooks.client.js:客户端 Hook。src/hooks.js:同时在客户端与服务端运行的 Hook。
这些模块中的代码会在应用启动时执行,适合初始化数据库客户端等工作。
handle
可添加到
src/hooks.server.js。
每当 SvelteKit 服务器收到请求时,此函数都会运行并决定响应,包括应用运行期间和预渲染期间。它接收表示请求的 event 对象,以及渲染路由并生成 Response 的 resolve 函数。你可以修改响应头、响应体,甚至完全绕过 SvelteKit,例如以编程方式实现路由。
/** @type {import('@sveltejs/kit/hooks').Handle} */
export async function handle({ event, resolve }) {
if (event.url.pathname.startsWith('/custom')) {
return new Response('custom response');
}
const response = await resolve(event);
return response;
}
import type { Handle } from '@sveltejs/kit/hooks';
export const handle: Handle = async ({ event, resolve }) => {
if (event.url.pathname.startsWith('/custom')) {
return new Response('custom response');
}
const response = await resolve(event);
return response;
};
静态资源请求(包括已经预渲染好的页面)不由 SvelteKit 处理。
如果 handle 由客户端发起的远程函数请求触发,route、params 和 url 表示调用远程函数的页面,而非 SvelteKit 为它创建的端点。不要用这些值判断用户是否有权访问数据,因为它们属于可被操纵的请求内容。导航时查询也不会自动重跑,除非导航导致查询参数改变,因此使用这些值时应留意这一点。
未实现时,默认行为为 ({ event, resolve }) => resolve(event)。
预渲染时,SvelteKit 会爬取页面中的链接并渲染发现的路由,调用 handle 及 load 等路由依赖。如果某些代码不应在此阶段执行,需先检查应用是否正在构建。
可以定义多个 handle,通过 sequence 辅助函数依次执行。
resolve 还接受可选的第二个参数,它是一个用于控制响应渲染的对象,可包含以下字段:
transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined>:自定义 HTML 转换。done为真表示最后一块。每一块不保证是完整合法的 HTML,例如可能只有开始标签;但一定在合理边界处分割,如%sveltekit.head%或布局/页面组件边界。filterSerializedResponseHeaders(name: string, value: string): boolean:决定load中通过fetch加载资源时,哪些响应头应进入序列化响应。默认不包含任何响应头。preload(input: { type: 'js' | 'css' | 'asset', path: string } | { type: 'font', path: string, filename: string }): boolean:决定预加载哪些文件。通常通过在<head>中添加<link>实现;启用output.linkHeaderPreload后,动态渲染页面改用Link响应头。构建代码块时发现的每个文件都会传给该方法,例如页面导入的 CSS。开发模式没有构建时分析,因此不会调用它。预加载可提前下载资源,但无谓下载过多也会降低性能。默认预加载 JS 和 CSS;目前不预加载 asset,后续可能根据反馈调整。字体输入还含有相对项目根目录的源文件路径filename,可用它代替哈希路径进行筛选。
/** @type {import('@sveltejs/kit/hooks').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('old', 'new'),
filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
preload: ({ type, path }) => type === 'js' || path.includes('/important/')
});
return response;
}
import type { Handle } from '@sveltejs/kit/hooks';
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('old', 'new'),
filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
preload: ({ type, path }) => type === 'js' || path.includes('/important/')
});
return response;
};
resolve(...) 不会抛出错误,而是始终返回带有相应状态码的 Promise<Response>。如果 handle 的其他位置抛错,SvelteKit 会将其视为致命错误,并根据 Accept 头返回 JSON 错误表示或备用错误页;后者可通过 src/error.html 自定义。详见错误处理文档。
locals
要向请求附加自定义数据,并传给 +server.js 处理器及服务端 load,可以填充 event.locals:
/** @type {import('@sveltejs/kit/hooks').Handle} */
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.cookies.get('sessionid'));
const response = await resolve(event);
// Note that modifying response headers isn't always safe.
// Response objects can have immutable headers
// (e.g. Response.redirect() returned from an endpoint).
// Modifying immutable headers throws a TypeError.
// In that case, clone the response or avoid creating a
// response object with immutable headers.
response.headers.set('x-custom-header', 'potato');
return response;
}
import type { Handle } from '@sveltejs/kit/hooks';
export const handle: Handle = async ({ event, resolve }) => {
event.locals.user = await getUserInformation(event.cookies.get('sessionid'));
const response = await resolve(event);
// Note that modifying response headers isn't always safe.
// Response objects can have immutable headers
// (e.g. Response.redirect() returned from an endpoint).
// Modifying immutable headers throws a TypeError.
// In that case, clone the response or avoid creating a
// response object with immutable headers.
response.headers.set('x-custom-header', 'potato');
return response;
};
handleFetch
可添加到
src/hooks.server.js。
此 Hook 允许修改或替换服务端(包括预渲染)中 event.fetch 的结果,调用位置可以是端点、load、action、handle、handleError 或 reroute。
例如,客户端导航到页面时,load 请求 https://api.yourapp.com;而在 SSR 期间,直接访问内部 API、绕过公网代理和负载均衡器可能更合适。
/** @type {import('@sveltejs/kit/hooks').HandleFetch} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// clone the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
import type { HandleFetch } from '@sveltejs/kit/hooks';
export const handleFetch: HandleFetch = async ({ request, fetch }) => {
if (request.url.startsWith('https://api.yourapp.com/')) {
// clone the original request, but change the URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
};
event.fetch 遵循浏览器凭据模型。同源请求会转发 cookie 与 authorization,除非 credentials 为 omit。跨源请求中,如果目标是应用域名的子域,也会包含 cookie,例如应用位于 my-domain.com,API 位于 api.my-domain.com。
例外是应用与 API 分别位于同级子域,例如 www.my-domain.com 与 api.my-domain.com。属于共同父域 my-domain.com 的 cookie 不会自动转发,因为 SvelteKit 不知道 cookie 所属的域。此时需在 handleFetch 中手动添加:
/** @type {import('@sveltejs/kit/hooks').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
if (request.url.startsWith('https://api.my-domain.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
}
return fetch(request);
}
import type { HandleFetch } from '@sveltejs/kit/hooks';
export const handleFetch: HandleFetch = async ({ event, request, fetch }) => {
if (request.url.startsWith('https://api.my-domain.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
}
return fetch(request);
};
handleError
可添加到
src/hooks.server.js和src/hooks.client.js。
加载、渲染或响应请求时发生的错误会调用此函数,允许你:
- 记录错误。
- 生成可安全展示给用户的错误表示,省略敏感消息或堆栈等细节。
除 event 外,Hook 还接收用于区分错误来源的 kind 和错误本身:
app:由应用的error(...)产生。error是符合App.Error的错误体,默认直接使用该错误体。framework:由 SvelteKit 产生,例如 404、405、413。error为{ status, message },其中 message 是Not Found等安全文本;默认返回相同对象。validation(仅服务端):远程函数参数未通过其 Standard Schema 校验。error为{ status: 400, message: 'Bad Request' },issues包含校验问题。默认只返回错误对象,除非显式返回,否则不会公开 issues。要访问校验库特有的字段,可给HandleServerError传入问题类型,例如HandleServerError<CustomIssue>。unknown:来源于你的代码或所调用代码,但具体原因未知。error是抛出的值,可能包含不宜公开的信息。默认返回{ status: 500, message: 'Internal Error' }。
下一节“Errors”进一步解释这些类别。重定向不是错误,不会进入该 Hook。
返回值应符合 App.Error;status 和 message 可省略,只有要覆盖以上默认值时才返回它们。
如果为
App.Error增加了必填属性,Hook 必须返回这些属性。
通过扩展 App.Error 接口,可以类型安全地向 page.error 添加信息。内置的 status 和 message 已经存在,无需重新声明。例如,添加一个跟踪 ID,便于用户联系支持人员时提供:
declare global {
namespace App {
interface Error {
errorId: string;
}
}
}
export {};
import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit/hooks').HandleServerError} */
export async function handleError({ kind, error, event }) {
if (kind === 'app') {
// you created this error with `error(...)`, so it already
// matches `App.Error` — pass it through unchanged
return error;
}
const errorId = crypto.randomUUID();
if (kind === 'framework') {
// a 404 (or similar) — `error.status` and `error.message` are safe to
// expose, so we keep them and just add our own property
return { ...error, errorId };
}
// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId }
});
// `status` and `message` are optional — we only override `message`,
// so the status stays at its default of 500
return {
message: 'Whoops!',
errorId
};
}
import * as Sentry from '@sentry/sveltekit';
import type { HandleServerError } from '@sveltejs/kit/hooks';
Sentry.init({/*...*/})
export const handleError: HandleServerError = async ({ kind, error, event }) => {
if (kind === 'app') {
// you created this error with `error(...)`, so it already
// matches `App.Error` — pass it through unchanged
return error;
}
const errorId = crypto.randomUUID();
if (kind === 'framework') {
// a 404 (or similar) — `error.status` and `error.message` are safe to
// expose, so we keep them and just add our own property
return { ...error, errorId };
}
// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId }
});
// `status` and `message` are optional — we only override `message`,
// so the status stays at its default of 500
return {
message: 'Whoops!',
errorId
};
};
import * as Sentry from '@sentry/sveltekit';
Sentry.init({/*...*/})
/** @type {import('@sveltejs/kit/hooks').HandleClientError} */
export async function handleError({ kind, error, event }) {
if (kind === 'app') {
return error;
}
const errorId = crypto.randomUUID();
if (kind === 'framework') {
return { ...error, errorId };
}
// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId }
});
return {
message: 'Whoops!',
errorId
};
}
import * as Sentry from '@sentry/sveltekit';
import type { HandleClientError } from '@sveltejs/kit/hooks';
Sentry.init({/*...*/})
export const handleError: HandleClientError = async ({ kind, error, event }) => {
if (kind === 'app') {
return error;
}
const errorId = crypto.randomUUID();
if (kind === 'framework') {
return { ...error, errorId };
}
// example integration with https://sentry.io/
Sentry.captureException(error, {
extra: { event, errorId }
});
return {
message: 'Whoops!',
errorId
};
};
在
src/hooks.client.js中,handleError的类型为HandleClientError,而非HandleServerError;event 为NavigationEvent,而非RequestEvent。
已经由服务端 Hook 转换过的错误,不会再次交给客户端 Hook。
开发期间,如果错误来自 Svelte 代码的语法错误,传入的 error 会附带一个突出显示错误位置的 frame 属性。
确保 handleError 本身永远不抛出错误。
init
可添加到
src/hooks.server.js和src/hooks.client.js。
服务器创建或浏览器应用启动时,此函数运行一次,适合执行初始化数据库连接等异步工作。
如果目标环境支持顶层
await,init与在模块顶层编写初始化逻辑并无本质区别;对不支持该特性的目标环境,应使用合适的兼容方式。原文将 Safari 列为需要留意的环境。
import * as db from '#lib/server/database.js';
/** @type {import('@sveltejs/kit/hooks').ServerInit} */
export async function init() {
await db.connect();
}
import * as db from '#lib/server/database.js';
import type { ServerInit } from '@sveltejs/kit/hooks';
export const init: ServerInit = async () => {
await db.connect();
};
浏览器中的异步
init会延迟水合,应谨慎选择其中的工作。
reroute
可添加到
src/hooks.js,在服务端和客户端均运行。
该函数在 handle 之前执行,用于改变 URL 到路由的映射。返回的路径名(默认 url.pathname)用于选择路由和参数。
例如,src/routes/[[lang]]/about/+page.svelte 需要通过 /en/about、/de/ueber-uns、/fr/a-propos 访问,可这样使用 reroute:
/** @type {Record<string, string>} */
const translated = {
'/en/about': '/en/about',
'/de/ueber-uns': '/de/about',
'/fr/a-propos': '/fr/about',
};
/** @type {import('@sveltejs/kit/hooks').Reroute} */
export function reroute({ url }) {
if (url.pathname in translated) {
return translated[url.pathname];
}
}
import type { Reroute } from '@sveltejs/kit/hooks';
const translated: Record<string, string> = {
'/en/about': '/en/about',
'/de/ueber-uns': '/de/about',
'/fr/a-propos': '/fr/about',
};
export const reroute: Reroute = ({ url }) => {
if (url.pathname in translated) {
return translated[url.pathname];
}
};
lang 参数会从返回的路径名中正确提取。
reroute 不会改变浏览器地址栏,也不会改变 event.url。
从 2.18 起,reroute 可以是异步函数,例如向后端获取路由决策。务必保持快速,否则会拖慢导航。需要获取数据时,应使用参数提供的 fetch,它具有 load 中 fetch 的同类优势;但由于尚未确定路由,handleFetch 无法获取 params 和 id。
/** @type {import('@sveltejs/kit/hooks').Reroute} */
export async function reroute({ url, fetch }) {
// Ask a special endpoint within your app about the destination
if (url.pathname === '/api/reroute') return;
const api = new URL('/api/reroute', url);
api.searchParams.set('pathname', url.pathname);
const result = await fetch(api).then(r => r.json());
return result.pathname;
}
import type { Reroute } from '@sveltejs/kit/hooks';
export const reroute: Reroute = async ({ url, fetch }) => {
// Ask a special endpoint within your app about the destination
if (url.pathname === '/api/reroute') return;
const api = new URL('/api/reroute', url);
api.searchParams.set('pathname', url.pathname);
const result = await fetch(api).then(r => r.json());
return result.pathname;
};
reroute被视为纯函数和幂等函数:相同输入必须得到相同输出,且不产生副作用。SvelteKit 基于这些假设在客户端缓存结果,因此每个唯一 URL 只调用一次。
transport
可添加到
src/hooks.js,在服务端和客户端均运行。
这是一个传输器集合,用于将 load 或表单 action 返回的自定义类型跨越服务端/客户端边界传输。每个传输器包含服务端编码值的 encode(非该类型实例时返回假值),以及对应的 decode:
import { Vector } from '#lib/math.js';
/** @type {import('@sveltejs/kit/hooks').Transport} */
export const transport = {
Vector: {
encode: (value) => value instanceof Vector && [value.x, value.y],
decode: ([x, y]) => new Vector(x, y)
}
};
import { Vector } from '#lib/math.js';
import type { Transport } from '@sveltejs/kit/hooks';
export const transport: Transport = {
Vector: {
encode: (value) => value instanceof Vector && [value.x, value.y],
decode: ([x, y]) => new Vector(x, y)
}
};
延伸阅读
- 教程:Hooks
在 GitHub 编辑本页;llms.txt 文档入口。











暂无评论内容