Skip to main content

Hooks

Edit this page on GitHub

'Hooks' は、特定のイベントに対して SvelteKit がレスポンスを呼び出すことを宣言するアプリ全体の関数で、これによってフレームワークの動作をきめ細やかに制御できるようになります。

hooks ファイルは2つあり、どちらもオプションです:

  • src/hooks.server.js — アプリのサーバーの hooks
  • src/hooks.client.js — アプリのクライアントの hooks

これらのモジュールのコードはアプリケーションの起動時に実行されるので、データベースクライアントの初期化などに有用です。

これらのファイルの場所は config.kit.files.hooks で設定できます。

Server hooks

以下の hooks は src/hooks.server.js に追加することができます:

handle

この関数は SvelteKit のサーバーが リクエスト を受けるたびに (アプリの実行中であろうと、プリレンダリングであろうと) 実行され、レスポンス を決定します。リクエストを表す event オブジェクトと、ルート(route)をレンダリングしレスポンスを生成する resolve という関数を受け取ります。これにより、レスポンスのヘッダーやボディを変更したり、SvelteKitを完全にバイパスすることができます (例えば、プログラムでルート(routes)を実装する場合など)。

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;
}
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 オブジェクトに埋め込んでください。

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

sequence ヘルパー関数を使用すると、複数の handle 関数呼び出しを追加することができます。

resolve はオプションの第2引数をサポートしており、レスポンスのレンダリング方法をより詳細にコントロールすることができます。そのパラメータは、以下のフィールドを持つオブジェクトです:

  • transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined> — カスタムの変換を HTML に適用します。done が true である場合、それは最後のチャンクです。チャンクが整形された HTML であることは保証されませんが (例えば、要素の開始タグは含むが終了タグは含まれない、など)、常に %sveltekit.head% やレイアウト(layout)/ページ(page)コンポーネントなどのような理にかなった境界 (sensible boundaries) で分割されます。
  • filterSerializedResponseHeaders(name: string, value: string): booleanload 関数が fetch でリソースを読み込むときに、シリアライズされるレスポンスにどのヘッダーを含めるかを決定します。デフォルトでは何も含まれません。
  • preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean<head> タグにどのファイルをプリロードの対象として追加するか決定します。このメソッドはビルド時、コードチャンクを構築している際に見つかったファイルごとに呼び出されます。これにより、例えば +page.svelteimport './styles.css がある場合、そのページに訪れたときにその CSS ファイルへの解決されたパスを以て preload が呼び出されるようになります。これはビルド時の分析によって行われるため、開発モードでは preload が呼ばれないことにご注意ください。プリロードによってその対象がより早くダウンロードされるようになるためパフォーマンスが改善しますが、不必要に多くのものをダウンロードしてしまうと、core web vitals を悪化させてしまいます。デフォルトでは、jscss ファイルがプリロードされます。現時点では asset ファイルはプリロードされませんが、フィードバックによっては追加されるかもしれません。
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;
}
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 中に他の場所でエラーがスローされた場合、それは致命的(fatal)なものとして扱われ、SvelteKit は Accept ヘッダーに応じて、そのエラーの JSON 表現か、src/error.html でカスタマイズ可能なフォールバックエラーページをレスポンスとして返します。エラーハンドリングの詳細は こちら からお読み頂けます。

handleFetch

この関数は、サーバー上で (またはプリレンダリング中に) 実行される load 関数の中で発生する fetch リクエストを変更 (または置換) することできます。

例えば、ユーザーがクライアントサイドでそれぞれのページに移動する際に、load 関数で https://api.yourapp.com のようなパブリックな URL にリクエストを行うかもしれませんが、SSR の場合には (パブリックなインターネットとの間にあるプロキシやロードバランサーをバイパスして) API を直接呼ぶほうが理にかなっているでしょう。

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/', 'http://localhost:9999/'),
request
);
}
 
return fetch(request);
}
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/', 'http://localhost:9999/'),
request
);
}
 
return fetch(request);
}

Credentials

同一オリジン(same-origin)リクエストの場合、SvelteKit の fetch 実装は、credentials オプションを "omit" にしない限り、 cookieauthorization ヘッダーを転送します。

クロスオリジン(cross-origin)リクエストの場合、リクエスト 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 を含める必要があります:

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'));
}
 
return fetch(request);
}

Shared hooks

以下は src/hooks.server.js src/hooks.client.js のどちらにも追加できます:

handleError

予期せぬエラーがロード中またはレンダリング中にスローされると、この関数が errorevent を引数にとって呼び出されます。これによって2つのことが可能になります:

  • エラーをログに残すことができます
  • エラーからメッセージやスタックトレースなどの機密情報を省略し、ユーザーに見せても安全なカスタムの表現を生成することができます。戻り値は $page.error の値となります。デフォルトでは、404 (event.route.idnull になっていることで検知できます) の場合は { message: 'Not Found' }、それ以外の場合は { message: 'Internal Error' } となります。これを型安全にするために、App.Error インターフェイスを宣言して、期待される形をカスタマイズすることができます (わかりやすいフォールバックの動作を保証するため、message: string を含めなければなりません)。

以下のコードは、エラーの形を { message: string; code: string } として型付けし、それを handleError 関数から適宜返す例を示しています:

ts
declare namespace App {
interface Error {
message: string;
code: string;
}
}
ts
/** @type {import('@sveltejs/kit').HandleServerError} */
export function handleError({ error, event }) {
// example integration with https://sentry.io/
Sentry.captureException(error, { event });
Type '{ message: string; code: any; }' is not assignable to type 'MaybePromise<void | Error>'. Object literal may only specify known properties, and 'code' does not exist in type 'Error | Promise<void | Error>'.
Property 'code' does not exist on type '{}'.
2322
2339
Type '{ message: string; code: any; }' is not assignable to type 'MaybePromise<void | Error>'. Object literal may only specify known properties, and 'code' does not exist in type 'Error | Promise<void | Error>'.
Property 'code' does not exist on type '{}'.
 
return {
message: 'Whoops!',
code: error?.code ?? 'UNKNOWN'
};
}
ts
import type { HandleServerError } from '@sveltejs/kit';
 
export const handleError: HandleServerError = ({ error, event }) => {
// example integration with https://sentry.io/
Sentry.captureException(error, { event });
Property 'code' does not exist on type '{}'.2339Property 'code' does not exist on type '{}'.
 
return {
message: 'Whoops!',
code: error?.code ?? 'UNKNOWN'
};
}
ts
/** @type {import('@sveltejs/kit').HandleClientError} */
export function handleError({ error, event }) {
// example integration with https://sentry.io/
Sentry.captureException(error, { event });
Type '{ message: string; code: any; }' is not assignable to type 'MaybePromise<void | Error>'. Object literal may only specify known properties, and 'code' does not exist in type 'Error | Promise<void | Error>'.
Property 'code' does not exist on type '{}'.
2322
2339
Type '{ message: string; code: any; }' is not assignable to type 'MaybePromise<void | Error>'. Object literal may only specify known properties, and 'code' does not exist in type 'Error | Promise<void | Error>'.
Property 'code' does not exist on type '{}'.
 
return {
message: 'Whoops!',
code: error?.code ?? 'UNKNOWN'
};
}
ts
import type { HandleClientError } from '@sveltejs/kit';
 
export const handleError: HandleClientError = ({ error, event }) => {
// example integration with https://sentry.io/
Sentry.captureException(error, { event });
Property 'code' does not exist on type '{}'.2339Property 'code' does not exist on type '{}'.
 
return {
message: 'Whoops!',
code: error?.code ?? 'UNKNOWN'
};
}

src/hooks.client.js では、handleError の型は HandleServerError ではなく HandleClientError で、eventRequestEvent ではなく NavigationEvent です。

この関数は 想定される エラー (@sveltejs/kit からインポートされる error 関数でスローされるエラー) の場合は呼び出されません。

開発中、Svelte のコードの構文エラーでエラーが発生した場合、渡される error には、エラーの場所のハイライトが付与された frame プロパティがあります。

handleError 自体が決してエラーをスローしないことを確認してください。