SvelteKit ルーティングでパスパラメータやクエリーパラメーターを使用する

SvelteKit のルーティングで URL パスパラメーターやクエリパラメーターの利用方法について纏めています。

やりたいこと

SvelteKit で URL パスパラメーターやクエリーパラメーターの値をページ内で使用したかったのですが、他の SPA や SSR 系と比較して独特だったので調べて公式ドキュメントの情報を纏めながら実装方法について記しておきます。

環境

OS
Microsoft Windows 21H2
Node.js
20.10.0
svelte
4.0.5
@sveltejs/kit
1.20.4

load 関数の作成

SvelteKit では +page.svelte ファイルと同階層に +page.js (TypeScript の場合は +page.ts) ファイルを配置できます。+page.jsload 関数をエクスポートし、load 関数の戻り値は +page.sveltedata プロパティとして受け取ることができます。

+page.svelte ファイルは、load 関数をエクスポートする +page.js という兄弟ファイルを持つことができ、load 関数の戻り値は page で data プロパティを介して使用することができます。 - Loading data • Docs • SvelteKit

この load 関数ですが Load 型として以下の通り定義されています。

type Load<
	Params extends Partial<Record<string, string>> = Partial<Record<string, string>>,
	InputData extends Record<string, unknown> | null = Record<string, any> | null,
	ParentData extends Record<string, unknown> = Record<string, any>,
	OutputData extends Record<string, unknown> | void = Record<string, any> | void,
	RouteId extends string | null = string | null
> = (event: LoadEvent<Params, InputData, ParentData, RouteId>) => MaybePromise<OutputData>;

Types • Docs • SvelteKit

上記を見ると load 関数は LoadEvent 型の event を引数として受け取るようです。

なお、実際に +page.ts 内で型を使用する場合は、上記 Load 型は使用せずに Generated Type を使用してください。詳細は後述します。

LoadEvent は以下のように定義されています。

interface LoadEvent<
	Params extends Partial<Record<string, string>> = Partial<
		Record<string, string>
	>,
	Data extends Record<string, unknown> | null = Record<
		string,
		any
	> | null,
	ParentData extends Record<string, unknown> = Record<
		string,
		any
	>,
	RouteId extends string | null = string | null
> extends NavigationEvent<Params, RouteId> {}

Types • Docs • SvelteKit

上記を見ると LoadEvent はさらに NavigationEvent を継承していることがわかります。

繰り返しになりますが、実際に +page.ts 内で型を使用する場合は、上記 LoadEvent 型は使用せずに Generated Type を使用してください。詳細は後述します。

NavigationEvent の定義は以下の通りです。

interface NavigationEvent<
	Params extends Partial<Record<string, string>> = Partial<
		Record<string, string>
	>,
	RouteId extends string | null = string | null
> {}

params: Params; 現在のページのパラメータ - 例えば /blog/[slug] というルート(route)の場合は、{ slug: string } オブジェクト

route: {…} 現在のルート(route)に関する情報

id: RouteId; 現在のルート(route)の ID - 例えば src/routes/blog/[slug] の場合は、/blog/[slug] となる

url: URL; 現在のページの URL

Types • Docs • SvelteKit

ここまで追うと params にパスパラメータがキーバリュー形式で格納されていることがわかります。さらに url に Web API 標準の URL (URL - Web API | MDN) が格納されているので、クエリーパラメーターもここから取得できそうです。

パスパラメータを取得

前項で、load 関数を用いてパスパラメーターを含む params やクエリーパラメーターを含む url が取得できることがわかりました。 実際に load 関数や、load 関数から data プロパティを受け取るページを実装してみます。ここでは ToDo リストから ToDo の ID を指定して表示するようなケースを想定します。

まず、load 関数をエクスポートする +page.ts を以下の通り作成します。

/src/route/todo/[id]/+page.ts
import type { PageLoad } from './$types.js';

export const load: PageLoad = ({ params }) => {
	return {
		id: params.id
	};
};

load 関数は PageLoad 型としています。前項で Load 型を紹介しましたが、そこでは Params, InputData, ParentData, OutputData, RouteId という 5 つのジェネリクスがありました。SvelteKit の Generated Type として、それらのジェネリクスにこのページ特有の型を当てはめて自動生成された型が、PageLoad 型です。

Generated Type については こちらの記事 で詳しく説明していますのでよかったらご参照ください。

.svelte-kit/types/src/todo/[id]/$types.d.ts を確認してみてください。ジェネリクス Params には、型 { id: string } が割り当てられており、その結果 5 行目の id: params.id のように型安全に取り扱うことができます。

次に +page.svelte を以下の通り作成します。ただ ID を表示するだけのページです。

/src/route/items/[id]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	export let data: PageData;
</script>

<div>{data.id}</div>

+page.tsload 関数の戻り値が data プロパティとして渡されます。 ここでも Generated Type である PageData を使用します。PageData+page.tsload 関数の戻り値の型が定義されています。

クエリーパラメーターを取得

クエリーパラメーターのケースも実装してみます。SvelteKit としての使い方はパスパラメータと大きく変わりません。ほぼ URL の使い方だけの世界です。ToDo リストからステータスをクエリーパラメーターで渡してフィルターするようなケースを想定します。

まず +page.ts です。 LoadEventurl は Web API 標準の URL (URL - Web API | MDN) ですので、searchParams プロパティの URLSearchParams (URLSearchParams - Web API | MDN) オブジェクトからクエリーパラメーターの値を取得します。

/src/route/todo/+page.ts
import type { PageLoad } from './$types.js';

export const load: PageLoad = ({ url }) => {
	return {
		status: url.searchParams.get('status') ?? ''
	};
};

+page.svelte の方は以下の通りでほとんど同じです。

/src/route/items/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	export let data: PageData;
</script>

<div>{data.status}</div>

load 関数をクライアントサイドのみで実行する

公式ドキュメントの以下の記載の通り、load 関数はサーバーでもブラウザでも実行されます。試しに load 関数内に console.log を仕込むとサーバー側の出力とブラウザ開発者ツールのコンソールの双方に出力されることが確認できます。

+page.js ファイルの load 関数はサーバーでもブラウザでも実行されます (ただし、export const ssr = false を設定した場合はブラウザでのみ実行されます)。 - Loading data • Docs • SvelteKit

上記公式ドキュメントの通り、+page.ts 内で ssr = false をエクスポートするとサーバーで実行されなくなります。

import type { PageLoad } from './$types.js';

export const ssr = false;

export const load: PageLoad = ({ params, url }) => {
	return {
		...
	}
}

同様に load 関数内に console.log を仕込むとサーバー側では出力されないことが確認できます。

サーバーサイドで各パラメーターを取り扱う

パスパラメータやクエリーパラメーターの値はサーバーサイドでデータベースへのクエリのパラメーターとして使用するケースが多いと思います。 このようなケースにおいては load 関数はサーバーサイドのみで実行したいはずです。

load 関数をサーバーサイドでのみ実行する場合は +page.ts の代わりに +page.server.tsload 関数を定義します。

サーバーサイドでのみ実行する場合、load 関数の型は ServerLoad 型 (Types • Docs • SvelteKit) となるので注意です。ただしこちらも実際に使用する型は Generated Type である PageServerLoad 型です。

import type { PageServerLoad } from './$types.js';

export const load: PageServerLoad = ({ params, url }) => {
	/**
	 * パスパラメータやクエリーパラメーターの値を使用して
	 * データベースからデータの取得等行う
	 */

	return {
		/** データベースから取得したデータ等を返す */
	};
};

あとがき

SvelteKit でのパスパラメーターやクエリーパラメーターの扱いについて最初少し戸惑いましたが、わかってしまえば簡単でページとデータロードも自然体で分離できて使い勝手よさそうだなと思いました。