跳至主要内容

高级

钩子

在 GitHub 上编辑此页面

“钩子”是您声明的应用范围函数,SvelteKit 将在特定事件响应中调用它,让您能够精细地控制框架的行为。

有三个钩子文件,全部都是可选的

  • src/hooks.server.js — 您的应用的服务器钩子
  • src/hooks.client.js — 您的应用的客户端钩子
  • src/hooks.js — 您的应用的钩子,在客户端和服务器上运行

这些模块中的代码将在应用程序启动时运行,因此非常适合初始化数据库客户端等。

您可以使用 config.kit.files.hooks 配置这些文件的位置。

服务器钩子

以下钩子可以添加到 src/hooks.server.js

handle

此函数在 SvelteKit 服务器每次收到 请求 时运行,无论是在应用运行期间还是 预渲染 期间,并确定 响应。它接收一个表示请求的 event 对象和一个名为 resolve 的函数,该函数渲染路由并生成一个 Response。这允许您修改响应头或正文,或完全绕过 SvelteKit(例如,用于以编程方式实现路由)。

src/hooks.server.js
ts
/** @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;
}
src/hooks.server.ts
ts
import type { Handle } from '@sveltejs/kit';
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 处理。

如果未实现,则默认为 ({ event, resolve }) => resolve(event)。要将自定义数据添加到请求(该数据会传递给 +server.js 中的处理程序和服务器 load 函数),请填充 event.locals 对象,如下所示。

src/hooks.server.js
ts
/** @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);
response.headers.set('x-custom-header', 'potato');
return response;
}
src/hooks.server.ts
ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
event.locals.user = await getUserInformation(event.cookies.get('sessionid'));
const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');
return response;
};

你可以定义多个 handle 函数,并使用 sequence 辅助函数 执行它们。

resolve 还支持第二个可选参数,它可以让你更好地控制如何渲染响应。该参数是一个对象,可以包含以下字段

  • transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined> — 对 HTML 应用自定义转换。如果 done 为 true,则它是最后一个块。无法保证块是格式良好的 HTML(例如,它们可能包含元素的开始标记,但不包含结束标记),但它们始终会在合理的边界处拆分,例如 %sveltekit.head% 或布局/页面组件。
  • filterSerializedResponseHeaders(name: string, value: string): boolean — 确定当 load 函数使用 fetch 加载资源时,应将哪些标头包含在序列化的响应中。默认情况下,不会包含任何标头。
  • preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean — 确定应将哪些文件添加到 <head> 标记中以预加载它。在构建时构造代码块时,会使用找到的每个文件调用该方法 — 因此,例如,如果你在 +page.svelte 中有 import './styles.css,则在访问该页面时,preload 将使用解析后的路径调用该 CSS 文件。请注意,在开发模式下,不会调用 preload,因为它依赖于在构建时发生的分析。预加载可以通过更早下载资产来提高性能,但如果下载了太多不必要的资产,它也会造成损害。默认情况下,将预加载 jscss 文件。目前根本不会预加载 asset 文件,但我们可能会在评估反馈后稍后添加此功能。
src/hooks.server.js
ts
/** @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;
}
src/hooks.server.ts
ts
import type { Handle } from '@sveltejs/kit';
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 将响应错误的 JSON 表示形式或备用错误页面 — 这可以通过 src/error.html 根据 Accept 标头进行自定义。你可以在 此处 了解更多有关错误处理的信息。

handleFetch

此函数允许你修改(或替换)在服务器上(或预渲染期间)运行的 loadaction 函数中发生的 fetch 请求。

例如,你的 load 函数在用户执行客户端导航到相应页面时可能会向公共 URL(如 https://api.yourapp.com)发出请求,但在 SSR 期间,直接访问 API(绕过位于 API 和公共互联网之间的任何代理和负载均衡器)可能更有意义。

src/hooks.server.js
ts
/** @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/', 'https://127.0.0.1:9999/'),
request
);
}
return fetch(request);
}
src/hooks.server.ts
ts
import type { HandleFetch } from '@sveltejs/kit';
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/', 'https://127.0.0.1:9999/'),
request,
);
}
return fetch(request);
};

凭据

对于同源请求,SvelteKit 的 fetch 实现将转发 cookieauthorization 标头,除非将 credentials 选项设置为 "omit"

对于跨源请求,如果请求 URL 属于应用程序的子域,则将包含 cookie — 例如,如果你的应用程序位于 my-domain.com 上,而你的 API 位于 api.my-domain.com 上,则请求中将包含 cookie。

如果你的应用程序和 API 位于同级子域上 — 例如 www.my-domain.comapi.my-domain.com — 那么属于公共父域(如 my-domain.com)的 cookie 将不会包含在内,因为 SvelteKit 无法知道 cookie 属于哪个域。在这些情况下,你需要使用 handleFetch 手动包含 cookie

src/hooks.server.js
ts
/** @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'));
Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.2345Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.
}
return fetch(request);
}
src/hooks.server.ts
ts
import type { HandleFetch } from '@sveltejs/kit';
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'));
Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.2345Argument of type 'string | null' is not assignable to parameter of type 'string'. Type 'null' is not assignable to type 'string'.
}
return fetch(request);
};

共享钩子

以下内容可以添加到 src/hooks.server.js src/hooks.client.js

handleError

如果在加载或渲染期间抛出意外错误,此函数将使用 erroreventstatus 代码和 message 调用。这允许两件事

  • 你可以记录错误
  • 你可以生成错误的自定义表示,该表示对用户来说是安全的,省略了敏感细节,如消息和堆栈跟踪。返回的值(默认为 { message })变为 $page.error 的值。

对于从你的代码(或你的代码调用的库代码)抛出的错误,状态将为 500,消息将为“内部错误”。虽然 error.message 可能包含不应向用户公开的敏感信息,但 message 是安全的(尽管对普通用户来说毫无意义)。

要以类型安全的方式向 $page.error 对象添加更多信息,你可以通过声明 App.Error 接口(必须包含 message: string,以保证合理的回退行为)来定制预期的形状。这允许你——例如——为用户追加一个跟踪 ID,以便与你的技术支持人员进行通信

src/app.d.ts
ts
declare global {
namespace App {
interface Error {
message: string;
errorId: string;
}
}
}
export {};
src/hooks.server.js
ts
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/
Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.2353Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: 'Whoops!',
errorId
};
}
src/hooks.server.ts
ts
import * as Sentry from '@sentry/sveltekit';
import type { HandleServerError } from '@sveltejs/kit';
Sentry.init({
/*...*/
});
export const handleError: HandleServerError = async ({ 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
ts
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/
Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.2353Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.
Sentry.captureException(error, {
extra: { event, errorId, status }
});
return {
message: 'Whoops!',
errorId
};
}
src/hooks.client.ts
ts
import * as Sentry from '@sentry/sveltekit';
import type { HandleClientError } from '@sveltejs/kit';
Sentry.init({
/*...*/
});
export const handleError: HandleClientError = async ({ 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,并且 eventNavigationEvent 而不是 RequestEvent

对于预期的错误(使用从 @sveltejs/kit 导入的 error 函数抛出的错误),不会调用此函数。

在开发过程中,如果由于 Svelte 代码中的语法错误而发生错误,则传入的错误会附加一个 frame 属性,突出显示错误的位置。

确保 handleError 永远不会抛出错误

通用钩子

以下内容可以添加到 src/hooks.js 中。通用钩子在服务器和客户端上运行(不要与特定于环境的共享钩子混淆)。

reroute

此函数在 handle 之前运行,允许你更改 URL 如何转换为路由。返回的路径名(默认为 url.pathname)用于选择路由及其参数。

例如,你可能有一个 src/routes/[[lang]]/about/+page.svelte 页面,该页面应可作为 /en/about/de/ueber-uns/fr/a-propos 访问。你可以使用 reroute 实现此功能

src/hooks.js
ts
/** @type {Record<string, string>} */
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];
}
}
src/hooks.ts
ts
import type { Reroute } from '@sveltejs/kit';
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 的值。

延伸阅读

上一个 高级路由
下一个 错误