こんにちは。よっしーです(^^)
今日は、SvelteKitでのフック処理について解説しています。
背景
SvelteKitでのフック処理について調査する機会がありましたので、その時の内容を備忘として記事に残しました。
フックとは
「フック」とは、特定のイベントに応じてSvelteKitが呼び出すアプリ全体で宣言する関数であり、フレームワークの動作を細かく制御することができます。
3つのフックファイルがあり、すべてオプションです:
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
/** @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によって処理されません。
handle
関数は、SvelteKitアプリケーションでサーバーサイドの処理をカスタマイズするための非常に重要な機能です。この関数を使用することで、リクエストの処理方法を細かく制御できます。
主なポイントは以下の通りです:
- すべてのリクエストに対して実行されます(プリレンダリング中も含む)。
- リクエスト(
event
)とレスポンス生成関数(resolve
)を受け取ります。 - レスポンスヘッダーやボディの修正、あるいはSvelteKitの通常の処理をバイパスすることができます。
- カスタムルートの実装にも使用できます。
event.locals
オブジェクトを使用して、カスタムデータをリクエストに追加できます。
この機能を使用することで、認証、ロギング、カスタムヘッダーの追加など、さまざまなサーバーサイドの処理をアプリケーション全体に適用することができます。
実装されていない場合、デフォルトは({ event, resolve }) => resolve(event)
となります。リクエストにカスタムデータを追加するには(これは+server.js
のハンドラーやサーバーのload
関数に渡されます)、以下のようにevent.locals
オブジェクトにデータを設定します。
src/hooks.server.js
/** @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;
}
複数のhandle
関数を定義し、sequence
ヘルパー関数を使用して実行することもできます。
resolve
関数は、レスポンスのレンダリング方法をより詳細に制御するための2つ目のオプションパラメータをサポートしています。このパラメータは以下のフィールドを持つオブジェクトです:
transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined>
— HTMLにカスタム変換を適用します。filterSerializedResponseHeaders(name: string, value: string): boolean
— シリアライズされたレスポンスに含めるヘッダーを決定します。preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean
—<head>
タグに追加してプリロードするファイルを決定します。
例:
/** @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<Response>
を返します。handle
内の他の場所でエラーがスローされた場合、それは致命的なものとして扱われ、SvelteKitはエラーのJSON表現またはフォールバックエラーページ(src/error.html
でカスタマイズ可能)で応答します。
handle
関数のより高度な使用方法と、resolve
関数の追加パラメータについて詳しく解説しています。主なポイントは以下の通りです:
event.locals
オブジェクトを使用してカスタムデータを追加する方法- 複数の
handle
関数をsequence
ヘルパー関数で実行する可能性 resolve
関数の第2引数として渡せるオプションの詳細
transformPageChunk
:HTMLの変換filterSerializedResponseHeaders
:シリアライズされたレスポンスヘッダーのフィルタリングpreload
:プリロードするファイルの決定
- エラー処理に関する注意点
これらの機能を使用することで、SvelteKitアプリケーションのサーバーサイド処理をより細かく制御し、パフォーマンスの最適化やカスタムの動作を実装することができます。
handleFetch
この関数を使用すると、サーバー上(またはプリレンダリング中)で実行されるload
またはaction
関数内で発生するfetch
リクエストを変更(または置換)することができます。
例えば、ユーザーがクライアントサイドでページにナビゲートする際に、load
関数がhttps://api.yourapp.com
のような公開URLにリクエストを行うかもしれません。しかし、SSR(サーバーサイドレンダリング)中には、APIに直接アクセスする(公開インターネットとの間にあるプロキシやロードバランサーをバイパスする)ことが理にかなっている場合があります。
src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// 元のリクエストをクローンし、URLを変更
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}
return fetch(request);
}
クレデンシャル
同一オリジンのリクエストの場合、SvelteKitの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を手動で含める必要があります:
src/hooks.server.js
/** @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);
}
handleFetch
関数は、SvelteKitアプリケーションでサーバーサイドのfetch
リクエストをカスタマイズするための重要な機能です。この関数を使用することで、APIリクエストの動作を細かく制御できます。
主なポイントは以下の通りです:
- サーバーサイドまたはプリレンダリング中の
fetch
リクエストを変更できます。 - APIエンドポイントの変更や、内部ネットワークへの直接アクセスなどに使用できます。
- クレデンシャル(cookieやauthorizationヘッダー)の扱いを制御できます。
特に注目すべき点は、クロスオリジンリクエストでのcookieの扱いです:
- アプリとAPIが同じドメインのサブドメインの場合、cookieは自動的に含まれます。
- アプリとAPIが兄弟サブドメインの場合、共通の親ドメインのcookieは自動的には含まれません。この場合、
handleFetch
を使用して手動でcookieを含める必要があります。
この機能を使用することで、開発環境と本番環境でのAPIアクセスの違いを吸収したり、セキュリティを強化したりすることができます。
おわりに
今日は、 SvelteKitでのフック処理について解説しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント