SvelteKitのhooksを深掘りした話(handle・handleError・handleFetch・sequence)
SvelteKitの hooks.server.ts に書く関数を整理します。認証以外の実用的なユースケースも含めて解説します。
hooks の種類
hooks.server.ts
├─ handle → 全リクエストを横断処理する
├─ handleError → 予期しないエラーをキャッチする
└─ handleFetch → load 関数内の fetch をインターセプト
hooks.client.ts
└─ handleError → クライアント側のエラーをキャッチする
① handle — 全リクエストを横断処理
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// リクエスト前の処理
console.log(`${event.request.method} ${event.url.pathname}`);
const response = await resolve(event);
// レスポンス後の処理
return response;
};
resolve(event) を呼ぶ前後に処理を挟めます。全リクエストに共通の処理を書く場所です。
実用的なユースケース:
- セッション確認・
localsへのユーザー情報のセット(認証) - 特定パスへのアクセス制限
- レスポンスヘッダーの追加(CORS・セキュリティヘッダー)
- リクエストのログ記録
② sequence — 複数の handle をまとめる
handle の処理が増えてきたときは sequence で分割・整理します。
// src/hooks.server.ts
import { sequence } from '@sveltejs/kit/hooks';
import type { Handle } from '@sveltejs/kit';
const auth: Handle = async ({ event, resolve }) => {
const sessionId = event.cookies.get('session_id');
if (sessionId) {
const db = event.platform?.env.DB;
const session = await db?.prepare(
'SELECT sessions.id, users.name FROM sessions JOIN users ON sessions.user_id = users.id WHERE sessions.id = ? AND sessions.expires_at > ?'
)
.bind(sessionId, Math.floor(Date.now() / 1000))
.first<{ id: string; name: string }>();
if (session) {
event.locals.user = { name: session.name };
} else {
event.locals.user = null;
event.cookies.delete('session_id', { path: '/' });
}
} else {
event.locals.user = null;
}
return resolve(event);
};
const logger: Handle = async ({ event, resolve }) => {
const start = Date.now();
const response = await resolve(event);
const duration = Date.now() - start;
console.log(`${event.request.method} ${event.url.pathname} ${response.status} ${duration}ms`);
return response;
};
export const handle = sequence(auth, logger);
sequence は上から順に実行されます。アクセスするたびにターミナルに以下のようなログが出力されます。
GET / 200 45ms
GET /posts/1 200 12ms
POST /login/github/callback 302 89ms
sequence を使うメリット:
- 認証・ログなど関心ごとにコードが分離される
- 新しい処理を追加するときは
sequence(auth, logger, 新しい処理)と並べるだけ - 各 handle を個別にテストしやすくなる
③ handleError — 予期しないエラーをキャッチ
import type { HandleServerError } from '@sveltejs/kit';
export const handleError: HandleServerError = async ({ error, event }) => {
// エラーをログサービスに送るなど
console.error('予期しないエラー:', error);
// +error.svelte に渡すメッセージを返す
return {
message: 'サーバーエラーが発生しました',
};
};
error() ヘルパーで意図的に投げたエラーはここには来ません。バグや予期しない例外だけがここに来ます。
④ handleFetch — load関数内のfetchをインターセプト
import type { HandleFetch } from '@sveltejs/kit';
export const handleFetch: HandleFetch = async ({ request, fetch }) => {
// 全fetchリクエストに認証ヘッダーを付ける
const modifiedRequest = new Request(request, {
headers: {
...Object.fromEntries(request.headers),
'Authorization': `Bearer ${process.env.API_TOKEN}`,
},
});
return fetch(modifiedRequest);
};
+page.server.ts の load 関数内で使う fetch をインターセプトできます。外部APIへのリクエストに共通のヘッダーを付けたいときに便利です。
まとめ
| hook | タイミング | 主な用途 |
|---|---|---|
handle |
全リクエスト | 認証・ログ・ヘッダー追加 |
sequence |
複数handleをつなげる | 処理の分離・整理 |
handleError |
予期しないエラー発生時 | エラーログ・エラーメッセージのカスタマイズ |
handleFetch |
load内のfetch | 共通ヘッダーの付与 |