钩子是你在应用程序范围内声明的函数,SvelteKit 会在响应特定事件时调用这些函数,从而让你能够精细控制框架的行为。 有三个钩子文件,都是可选的: * `src/hooks.server.js` — 您的应用程序的服务器钩子 * `src/hooks.client.js` — 您的应用程序客户端钩子 * `src/hooks.js` — 您的应用程序在客户端和服务器上运行的钩子 代码在这些模块中将在应用程序启动时运行,使它们对于初始化数据库客户端等非常有用。 > \[!注意\] 您可以使用[`config.kit.files.hooks`](configuration#files)配置这些文件的存储位置。 ## 服务器钩子 以下钩子可以添加到 `src/hooks.server.js` 中: ### 处理 这个函数在 SvelteKit 服务器每次接收到[请求](web-standards#Fetch-APIs-Request)时都会运行——无论是在应用运行期间,还是在[预渲染](page-options#prerender)期间——并确定[响应](web-standards#Fetch-APIs-Response)。它接收一个表示请求的``` 事件对象和一个名为`` resolve的函数,该函数渲染路由并生成`Response。这允许您修改响应头或正文,或者完全绕过 SvelteKit(例如,用于程序化实现路由)。` `` ``` ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').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; } ``` > \[注意\] 对于静态资源请求——包括已预渲染的页面——SvelteKit *不*进行处理。 如果未实现,则默认为 `({ event, resolve }) => resolve(event)` 。 在预渲染期间,SvelteKit 会爬取您的页面以查找链接,并为找到的每个路由进行渲染。渲染路由会调用`handle`函数(以及所有其他路由依赖项,如`load`)。如果您需要在此阶段排除某些代码的运行,请确保应用不是在[`构建`]($app-environment#building)。 ### 本地 要向请求中添加自定义数据,这些数据将传递到在 `+server.js` 和服务器 `load` 函数中的处理器,请填充 `event.locals` 对象,如下所示。 ```js /// file: src/hooks.server.js // @filename: ambient.d.ts type User = { name: string; } declare namespace App { interface Locals { user: User; } } const getUserInformation: (cookie: string | void) => Promise; // @filename: index.js // ---cut--- /** @type {import('@sveltejs/kit').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; } ``` 您可以为多个 `handle` 函数定义,并使用 [序列 `helper` 函数](@sveltejs-kit-hooks) [执行它们](@sveltejs-kit-hooks)。 `resolve` 同样支持第二个可选参数,该参数可以让你更灵活地控制响应的渲染方式。该参数是一个对象,可以包含以下字段: * `transformPageChunk(opts: { html: string, done: boolean }): MaybePromise` — 对 HTML 应用自定义转换。如果`done`为 true,则是最后一个块。块不保证是良好形成的 HTML(例如,它们可能包含一个元素的打开标签但没有其关闭标签)但它们将始终在合理的边界处分割,如`%sveltekit.head%`或布局/页面组件。 * `filterSerializedResponseHeaders(name: string, value: string): boolean` — 确定在 `load` 函数使用 `fetch` 加载资源时,应包含哪些头信息在序列化响应中。默认情况下,不包含任何头信息。 * `preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean` — 确定哪些文件应该添加到 `` 标签中以预加载它。该方法在构建时构建代码块时,对每个找到的文件调用 — 因此,如果您例如在 `+page.svelte` 中有 `import './styles.css'`,则在访问该页面时,`preload` 将调用该 CSS 文件的解析路径。请注意,在开发模式下,`preload` 不会 *调用*,因为它依赖于构建时发生的分析。预加载可以通过提前下载资源来提高性能,但如果不必要地下载过多,也可能造成伤害。默认情况下,将预加载 `js` 和 `css` 文件。目前,`asset` 文件根本不会预加载,但我们在评估反馈后可能会添加此功能。 ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').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; } ``` 请注意,`resolve(...)`永远不会抛出错误,它总是返回一个带有适当状态码的`Promise`。如果在`handle`过程中其他地方抛出错误,它将被视为致命错误,SvelteKit 将以错误或备用错误页面的 JSON 表示形式响应——该页面可以通过`src/error.html`进行自定义——具体取决于`Accept`头。您可以在[这里](errors)了解更多关于错误处理的信息。 ### 处理获取 此功能允许您修改(或替换)在端点内运行的 [`event.fetch`](load#Making-fetch-requests) 调用的结果,该调用在服务器上(或在预渲染期间)执行,包括 `load`、`action`、`handle`、`handleError` 或 `reroute`。 例如,您的 `load` 函数可能在用户执行客户端导航到相应页面时向公共 URL(如 `https://api.yourapp.com`)发起请求,但在 SSR 过程中,直接调用 API(绕过位于其与公共互联网之间的任何代理和负载均衡器)可能更有意义。 ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').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); } ``` 请求使用`event.fetch`的请求遵循浏览器的凭据模型——对于同源请求,除非将`credentials`选项设置为`"omit"`,否则将转发`cookie`和`authorization`头部。对于跨源请求,如果请求 URL 属于应用的子域,则将包含`cookie`——例如,如果您的应用位于`my-domain.com`,而您的 API 位于`api.my-domain.com`,则 cookie 将包含在请求中。 有一个注意事项:如果您的应用程序和 API 位于兄弟子域名上——例如 `www.my-domain.com` 和 `api.my-domain.com` ——那么属于公共父域(如 `my-domain.com`)的 cookie 将不会包含在内,因为 SvelteKit 无法知道 cookie 属于哪个域名。在这种情况下,您需要手动使用 `handleFetch` 包含 cookie。 ```js /// file: src/hooks.server.js // @errors: 2345 /** @type {import('@sveltejs/kit').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); } ``` ## 共享钩子 以下可以添加到 `src/hooks.server.js`*和*`src/hooks.client.js`: ### 处理错误 如果在加载、渲染或从端点抛出[意外错误](errors#Unexpected-errors)时,此函数将使用`错误`、`事件`、`状态`代码和`消息`被调用。这允许做两件事: * 您可以记录错误 * 您可以为用户生成一个安全的错误自定义表示,省略敏感细节,如消息和堆栈跟踪。返回的值默认为`{消息}`,成为`$page.error`的值。 对于从您的代码(或被您的代码调用的库代码)抛出的错误,状态将为 500,信息为“内部错误”。虽然`error.message`可能包含不应向用户暴露的敏感信息,但`message`是安全的(尽管对普通用户来说可能没有意义)。 要安全地向 `$page.error` 对象添加更多信息,您可以通过声明一个 `App.Error` 接口(必须包含 `message: string`,以确保合理的回退行为)来自定义期望的形状。这允许您——例如——为用户提供一个跟踪 ID,以便在与技术支持人员沟通时引用: ```ts /// file: src/app.d.ts declare global { namespace App { interface Error { message: string; errorId: string; } } } export {}; ``` ```js /// file: src/hooks.server.js // @errors: 2322 2353 // @filename: ambient.d.ts declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js // ---cut--- import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleServerError} */ export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); // example integration with https://sentry.io/ Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: 'Whoops!', errorId }; } ``` ```js /// file: src/hooks.client.js // @errors: 2322 2353 // @filename: ambient.d.ts declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js // ---cut--- import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleClientError} */ export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); // example integration with https://sentry.io/ Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: 'Whoops!', errorId }; } ``` > \[!注意\] 在 `src/hooks.client.js` 中,`handleError` 的类型为 `HandleClientError` 而不是 `HandleServerError`,并且 `event` 是 `NavigationEvent` 而不是 `RequestEvent`。 此函数不会为*预期*错误(使用从`@sveltejs/kit`导入的`error`函数抛出的错误)调用。 在开发过程中,如果由于您的 Svelte 代码中的语法错误导致发生错误,传入的错误将附加一个`frame`属性,突出显示错误的位置。 > \[!注意\] 确保以下代码 `handleError`*从不* 抛出错误 ### 初始化 此函数在服务器创建或浏览器中的应用启动时运行一次,是执行异步工作(如初始化数据库连接)的有用位置。 > \[注意\] 如果您的环境支持顶级 await,那么 `init` 函数实际上与在模块顶层编写初始化逻辑并没有太大区别,但某些环境——最值得注意的是,Safari——并不支持。 ```js /// file: src/hooks.server.js import * as db from '$lib/server/database'; /** @type {import('@sveltejs/kit').ServerInit} */ export async function init() { await db.connect(); } ``` > \[!注意\] 在浏览器中,`init` 中的异步工作将延迟初始化,因此请注意您放入那里的内容。 ## 通用钩子 以下可以添加到 `src/hooks.js`。通用钩子在服务器和客户端上运行(不要与特定环境的共享钩子混淆)。 ### 重定向 此函数在 `handle` 之前运行,并允许您更改如何将 URL 转换为路由。返回的路径名(默认为 `url.pathname`)用于选择路由及其参数。 例如,您可能有一个 `src/routes/[[lang]]/about/+page.svelte` 页面,它应该可以通过 `/en/about` 或 `/de/ueber-uns` 或 `/fr/a-propos` 访问。您可以使用 `reroute` 来实现这一点: ```js /// file: src/hooks.js // @errors: 2345 // @errors: 2304 /** @type {Record} */ const translated = { '/en/about': '/en/about', '/de/ueber-uns': '/de/about', '/fr/a-propos': '/fr/about', }; /** @type {import('@sveltejs/kit').Reroute} */ export function reroute({ url }) { if (url.pathname in translated) { return translated[url.pathname]; } } ``` The `lang` parameter will be correctly derived from the returned pathname. 使用`reroute`不会改变浏览器地址栏的内容,或`event.url`的值。*不会* 自 2.18 版本起,`reroute`钩子可以异步执行,允许它(例如)从您的后端获取数据以决定重定向到何处。请谨慎使用,并确保其快速执行,否则它将延迟导航。如果您需要获取数据,请使用作为参数提供的`fetch`。它与提供给`load`函数的`fetch`具有相同的优势,但有一个前提是`params`和`id`对于[`handleFetch`](#Server-hooks-handleFetch)不可用,因为路由尚未确定。 ```js /// file: src/hooks.js // @errors: 2345` // @errors: 2304 /** @type {import('@sveltejs/kit').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; } ``` > \[注意\] `reroute` 被视为一个纯净的幂等函数。因此,它必须始终对相同的输入返回相同的输出,并且不能有副作用。在这些假设下,SvelteKit 在客户端缓存了`reroute`的结果,因此它只对每个唯一的 URL 调用一次。 ### 运输 这是一个包含 *运输器* 的集合,允许您传递自定义类型——从 `加载` 和表单操作返回的类型——跨越服务器/客户端边界。每个运输器包含一个 `编码` 函数,该函数在服务器上编码值(或对于不是该类型实例的任何内容返回一个假值)以及相应的 `解码` 函数: ```js /// file: src/hooks.js import { Vector } from '$lib/math'; /** @type {import('@sveltejs/kit').Transport} */ export const transport = { Vector: { encode: (value) => value instanceof Vector && [value.x, value.y], decode: ([x, y]) => new Vector(x, y) } }; ``` ## 进一步阅读 * [ 教程:钩子](/tutorial/kit/handle)