Advanced
Hooks
Edit this page on GitHub'Hooks' は、特定のイベントに対して SvelteKit がレスポンスを呼び出すことを宣言するアプリ全体の関数で、これによってフレームワークの動作をきめ細やかに制御できるようになります。
hooks ファイルは2つあり、どちらもオプションです:
src/hooks.server.js
— アプリのサーバーの hookssrc/hooks.client.js
— アプリのクライアントの hooks
これらのモジュールのコードはアプリケーションの起動時に実行されるので、データベースクライアントの初期化などに有用です。
これらのファイルの場所は
config.kit.files.hooks
で設定できます。
Server hookspermalink
以下の hooks は src/hooks.server.js
に追加することができます:
handlepermalink
この関数は SvelteKit のサーバーが リクエスト を受けるたびに (アプリの実行中であろうと、プリレンダリングであろうと) 実行され、レスポンス を決定します。リクエストを表す event
オブジェクトと、ルート(route)をレンダリングしレスポンスを生成する resolve
という関数を受け取ります。これにより、レスポンスのヘッダーやボディを変更したり、SvelteKitを完全にバイパスすることができます (例えば、プログラムでルート(routes)を実装する場合など)。
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {if (event .url .pathname .startsWith ('/custom')) {return newResponse ('custom response');}constresponse = awaitresolve (event );returnresponse ;}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {if (event .url .pathname .startsWith ('/custom')) {return newResponse ('custom response');}constresponse = awaitresolve (event );returnresponse ;};
静的アセット(プリレンダリング済みのページを含む)に対するリクエストは SvelteKit では処理されません。
未実装の場合、デフォルトは ({ event, resolve }) => resolve(event)
となります。カスタムデータをリクエストに追加し、+server.js
のハンドラーやサーバー(server) load
関数に渡すには、以下のように event.locals
オブジェクトに埋め込んでください。
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {event .locals .user = awaitgetUserInformation (event .cookies .get ('sessionid'));constresponse = awaitresolve (event );response .headers .set ('x-custom-header', 'potato');returnresponse ;}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {event .locals .user = awaitgetUserInformation (event .cookies .get ('sessionid'));constresponse = awaitresolve (event );response .headers .set ('x-custom-header', 'potato');returnresponse ;};
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): boolean
—load
関数がfetch
でリソースを読み込むときに、シリアライズされるレスポンスにどのヘッダーを含めるかを決定します。デフォルトでは何も含まれません。preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean
—<head>
タグにどのファイルをプリロードの対象として追加するか決定します。このメソッドはビルド時、コードチャンクを構築している際に見つかったファイルごとに呼び出されます。これにより、例えば+page.svelte
にimport './styles.css
がある場合、そのページに訪れたときにその CSS ファイルへの解決されたパスを以てpreload
が呼び出されるようになります。これはビルド時の分析によって行われるため、開発モードではpreload
が呼ばれないことにご注意ください。プリロードによってその対象がより早くダウンロードされるようになるためパフォーマンスが改善しますが、不必要に多くのものをダウンロードしてしまうと、core web vitals を悪化させてしまいます。デフォルトでは、js
、css
ファイルがプリロードされます。現時点ではasset
ファイルはプリロードされませんが、フィードバックによっては追加されるかもしれません。
ts
/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {constresponse = awaitresolve (event , {transformPageChunk : ({html }) =>html .replace ('old', 'new'),filterSerializedResponseHeaders : (name ) =>name .startsWith ('x-'),preload : ({type ,path }) =>type === 'js' ||path .includes ('/important/')});returnresponse ;}
ts
import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {constresponse = awaitresolve (event , {transformPageChunk : ({html }) =>html .replace ('old', 'new'),filterSerializedResponseHeaders : (name ) =>name .startsWith ('x-'),preload : ({type ,path }) =>type === 'js' ||path .includes ('/important/'),});returnresponse ;};
resolve(...)
は決してエラーをスローせず、適切なステータスコードと Promise<Response>
を返すことにご注意ください。もし handle
中に他の場所でエラーがスローされた場合、それは致命的(fatal)なものとして扱われ、SvelteKit は Accept
ヘッダーに応じて、そのエラーの JSON 表現か、src/error.html
でカスタマイズ可能なフォールバックエラーページをレスポンスとして返します。エラーハンドリングの詳細は こちら からお読み頂けます。
handleFetchpermalink
この関数は、サーバー上で (またはプリレンダリング中に) 実行される load
関数や action
関数の中で発生する fetch
リクエストを変更 (または置換) することできます。
例えば、ユーザーがクライアントサイドでそれぞれのページに移動する際に、load
関数で https://api.yourapp.com
のようなパブリックな URL にリクエストを行うかもしれませんが、SSR の場合には (パブリックなインターネットとの間にあるプロキシやロードバランサーをバイパスして) API を直接呼ぶほうが理にかなっているでしょう。
ts
/** @type {import('@sveltejs/kit').HandleFetch} */export async functionhandleFetch ({request ,fetch }) {if (request .url .startsWith ('https://api.yourapp.com/')) {// clone the original request, but change the URLrequest = newRequest (request .url .replace ('https://api.yourapp.com/', 'http://localhost:9999/'),request );}returnfetch (request );}
ts
import type {HandleFetch } from '@sveltejs/kit';export consthandleFetch :HandleFetch = async ({request ,fetch }) => {if (request .url .startsWith ('https://api.yourapp.com/')) {// clone the original request, but change the URLrequest = newRequest (request .url .replace ('https://api.yourapp.com/', 'http://localhost:9999/'),request ,);}returnfetch (request );};
Credentials
同一オリジン(same-origin)リクエストの場合、SvelteKit の fetch
実装は、credentials
オプションを "omit"
にしない限り、 cookie
と authorization
ヘッダーを転送します。
クロスオリジン(cross-origin)リクエストの場合、リクエスト 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 を含める必要があります:
ts
/** @type {import('@sveltejs/kit').HandleFetch} */export async functionhandleFetch ({event ,request ,fetch }) {if (request .url .startsWith ('https://api.my-domain.com/')) {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'.request .headers .set ('cookie',event .request .headers .get ('cookie'));}returnfetch (request );}
ts
import type {HandleFetch } from '@sveltejs/kit';export consthandleFetch :HandleFetch = async ({event ,request ,fetch }) => {if (request .url .startsWith ('https://api.my-domain.com/')) {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'.request .headers .set ('cookie',event .request .headers .get ('cookie'));}returnfetch (request );};
Shared hookspermalink
以下は src/hooks.server.js
と src/hooks.client.js
のどちらにも追加できます:
handleErrorpermalink
予期せぬエラーがロード中またはレンダリング中にスローされると、この関数が error
と event
を引数にとって呼び出されます。これによって2つのことが可能になります:
- エラーをログに残すことができます
- エラーからメッセージやスタックトレースなどの機密情報を省略し、ユーザーに見せても安全なカスタムの表現を生成することができます。戻り値は
$page.error
の値となります。デフォルトでは、404 (event.route.id
がnull
になっていることで検知できます) の場合は{ message: 'Not Found' }
、それ以外の場合は{ message: 'Internal Error' }
となります。これを型安全にするために、App.Error
インターフェイスを宣言して、期待される形をカスタマイズすることができます (わかりやすいフォールバックの動作を保証するため、message: string
を含めなければなりません)。
以下のコードは、エラーの形を { message: string; errorId: string }
として型付けし、それを handleError
関数から適宜返す例を示しています:
ts
declareglobal {namespaceApp {interfaceError {message : string;errorId : string;}}}export {};
ts
import * asSentry from '@sentry/node';importcrypto from 'crypto';Sentry .init ({/*...*/})/** @type {import('@sveltejs/kit').HandleServerError} */export async functionType '{ message: string; errorId: string; }' is not assignable to type 'void | Error'. Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.2322Type '{ message: string; errorId: string; }' is not assignable to type 'void | Error'. Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.handleError ({error ,event }) {consterrorId =crypto .randomUUID ();// example integration with https://sentry.io/Sentry .captureException (error , {extra : {event ,errorId } });return {message : 'Whoops!',errorId };}
ts
import * asSentry from '@sentry/node';importcrypto from 'crypto';import type {HandleServerError } from '@sveltejs/kit';Sentry .init ({/*...*/});export consthandleError :HandleServerError = async ({error ,event }) => {consterrorId =crypto .randomUUID ();// example integration with https://sentry.io/Sentry .captureException (error , {extra : {event ,errorId } });return {message : 'Whoops!',errorId ,};};
ts
import * asSentry from '@sentry/svelte';Sentry .init ({/*...*/})/** @type {import('@sveltejs/kit').HandleClientError} */export async functionType '{ message: string; errorId: string; }' is not assignable to type 'void | Error'. Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.2322Type '{ message: string; errorId: string; }' is not assignable to type 'void | Error'. Object literal may only specify known properties, and 'errorId' does not exist in type 'Error'.handleError ({error ,event }) {consterrorId =crypto .randomUUID ();// example integration with https://sentry.io/Sentry .captureException (error , {extra : {event ,errorId } });return {message : 'Whoops!',errorId };}
ts
import * asSentry from '@sentry/svelte';import type {HandleClientError } from '@sveltejs/kit';Sentry .init ({/*...*/});export consthandleError :HandleClientError = async ({error ,event }) => {consterrorId =crypto .randomUUID ();// example integration with https://sentry.io/Sentry .captureException (error , {extra : {event ,errorId } });return {message : 'Whoops!',errorId ,};};
src/hooks.client.js
では、handleError
の型はHandleServerError
ではなくHandleClientError
で、event
はRequestEvent
ではなくNavigationEvent
です。
この関数は 想定される エラー (@sveltejs/kit
からインポートされる error
関数でスローされるエラー) の場合は呼び出されません。
開発中、Svelte のコードの構文エラーでエラーが発生した場合、渡される error には、エラーの場所のハイライトが付与された frame
プロパティがあります。
handleError
自体が決してエラーをスローしないようにしてください。