高度なルーティング
Restパラメータ
ルートセグメント(route segments)の数がわからない場合は、rest 構文を使用することができます。例えば GitHub のファイルビューアのようなものを実装する場合は…
/[org]/[repo]/tree/[branch]/[...file]
…この場合、/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md
をリクエストすると、以下のパラメータをページで使うことができます:
{
org: 'sveltejs',
repo: 'kit',
branch: 'main',
file: 'documentation/docs/04-advanced-routing.md'
}
src/routes/a/[...rest]/z/+page.svelte
は/a/z
にも (つまり、パラメータが全くない場合にも)、/a/b/z
や/a/b/c/z
と同様にマッチします。Rest パラメータの値が正しいことを、例えば matcher を使用するなどして確認してください。
404 pages
Rest パラメータによってカスタムの 404 をレンダリングすることができます。これらのルート(routes)があるとして…
src/routes/
├ marx-brothers/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
…もし /marx-brothers/karl
にリクエストしても、marx-brothers/+error.svelte
ファイルはレンダリング されません 。なぜならどのルート(route) にもマッチしないからです。もしネストしたエラーページをレンダリングしたければ、どんな /marx-brothers/*
リクエストにもマッチするルート(route)を作成し、そこから 404 を返すようにしてください:
src/routes/
├ marx-brothers/
| ├ [...path]/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function function load(event: any): void
load(event: any
event) {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not Found');
}
import { function error(status: number, body: App.Error): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error } from '@sveltejs/kit';
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event) => {
function error(status: number, body?: {
message: string;
} extends App.Error ? App.Error | string | undefined : never): never (+1 overload)
Throws an error with a HTTP status code and an optional message.
When called during request handling, this will cause SvelteKit to
return an error response without invoking handleError
.
Make sure you’re not catching the thrown error, which would prevent SvelteKit from handling it.
error(404, 'Not Found');
};
もし 404 のケースをハンドリングしていない場合、
handleError
によって表示が行われます。
Optional parameters
[lang]/home
というルートに含まれる lang
というパラメータは必須です。これらのパラメータをオプションにできると、今回の例では home
と en/home
のどちらも同じページを指すことができるのでとても便利です。パラメータにもう1つ括弧を付けることでこれができるようになります: [[lang]]/home
optional のルートパラメータ(route parameter)は rest パラメータに続けて使用すること ([...rest]/[[optional]]
) はできません。パラメータは ‘greedily’ にマッチし、optional のパラメータは使用されないこともあるためです。
Matching
src/routes/fruits/[page]
というルート(route)は /fruits/apple
にマッチしますが、/fruits/rocketship
にもマッチしてしまいます。これを防ぎたい場合、パラメータ文字列("apple"
や "rocketship"
)を引数に取ってそれが有効なら true
を返す matcher を params
ディレクトリに追加することで、ルート(route)のパラメータを適切に定義することができます…
/**
* @param {string} param
* @return {param is ('apple' | 'orange')}
* @satisfies {import('@sveltejs/kit').ParamMatcher}
*/
export function function match(param: any): boolean
match(param: any
param) {
return param: any
param === 'apple' || param: any
param === 'orange';
}
import type { type ParamMatcher = (param: string) => boolean
The shape of a param matcher. See matching for more info.
ParamMatcher } from '@sveltejs/kit';
export const const match: (param: string) => param is ("apple" | "orange")
match = ((param: string
param: string): param: string
param is ('apple' | 'orange') => {
return param: string
param === 'apple' || param: string
param === 'orange';
}) satisfies type ParamMatcher = (param: string) => boolean
The shape of a param matcher. See matching for more info.
ParamMatcher;
…そしてルート(routes)を拡張します:
src/routes/fruits/[page=fruit]
もしパス名がマッチしない場合、SvelteKit は (後述のソート順の指定に従って) 他のルートでマッチするか試行し、どれにもマッチしない場合は最終的に 404 を返します。
params
ディレクトリにある各モジュールは matcher に対応しています。ただし、matcher のユニットテストに使用される *.test.js
と *.spec.js
ファイルは例外です。
Matcher は サーバーとブラウザの両方で動作します。
ソート(Sorting)
あるパスに対し、マッチするルート(routes)は複数でも構いません。例えば、これらのルート(routes)はどれも /foo-abc
にマッチします:
src/routes/[...catchall]/+page.svelte
src/routes/[[a=x]]/+page.svelte
src/routes/[b]/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/foo-abc/+page.svelte
SvelteKit は、どのルート(route)に対してリクエストされているのかを判断しなければなりません。そのため、以下のルールに従ってこれらをソートします…
- より詳細・明確(specific)なルート(routes)ほど、より優先度が高い (例えば、動的なパラメータが1つあるルートより、パラメータのないルートのほうがより詳細・明確(specific)である、など)
- matchers 付きのパラメータ (
[name=type]
) は matchers なしのパラメータ ([name]
) よりも優先度が高い [[optional]]
と[...rest]
パラメータはルート(route)の最後の部分でない限り無視される (最後の部分になっている場合は最も低い優先度として扱われる)。言い換えると、ソートの目的上、x/[[y]]/z
とx/z
は同等に扱われる- 優先度が同じ場合はアルファベット順で解決される
…この順序で並べると、/foo-abc
の場合は src/routes/foo-abc/+page.svelte
を呼び出し、/foo-def
の場合は src/routes/foo-[c]/+page.svelte
を呼び出します:
src/routes/foo-abc/+page.svelte
src/routes/foo-[c]/+page.svelte
src/routes/[[a=x]]/+page.svelte
src/routes/[b]/+page.svelte
src/routes/[...catchall]/+page.svelte
エンコード(Encoding)
ファイルシステムでは使用できない文字があります — Linux と Mac では /
、Windows では \ / : * ? " < > |
です。URL においては、#
と %
には特別な意味がありますし、SvelteKit においては [ ] ( )
に特別な意味があります。そのため、これらの文字をそのままルート(route)に使用することはできません。
これらの文字をルート(route)に使用するには、16進数のエスケープシーケンスを使います。[x+nn]
というフォーマットで、nn
の部分は16進数の文字コードです:
\
—[x+5c]
/
—[x+2f]
:
—[x+3a]
*
—[x+2a]
?
—[x+3f]
"
—[x+22]
<
—[x+3c]
>
—[x+3e]
|
—[x+7c]
#
—[x+23]
%
—[x+25]
[
—[x+5b]
]
—[x+5d]
(
—[x+28]
)
—[x+29]
例えば、/smileys/:-)
というルート(route)を作る場合は、src/routes/smileys/[x+3a]-[x+29]/+page.svelte
ファイルを作成します。
JavaScript を使って文字の16進数コードを判定することができます:
':'.String.charCodeAt(index: number): number
Returns the Unicode value of the character at the specified location.
charCodeAt(0).Number.toString(radix?: number): string
Returns a string representation of an object.
toString(16); // '3a', hence '[x+3a]'
また、Unicode のエスケープシーケンスを使用することもできます。通常、エンコードされていない文字を直接使用することができるので、こうする必要はありませんが、何らかの理由で、例えばファイル名に絵文字を使用することができない場合、エスケープ文字を使用することができます。言い換えると、以下は同じことをしているということです:
src/routes/[u+d83e][u+dd2a]/+page.svelte
src/routes/🤪/+page.svelte
Unicode エスケープシーケンスのフォーマットは [u+nnnn]
で、nnnn
の部分は 0000
から 10ffff
までの適切な値です (JavaScript の文字列エスケープとは異なり、ffff
以上のコードポイントを表現するためにサロゲートペアを使用する必要はありません)。Unicode エンコーディングについてもっと知りたい方は、Programming with Unicode を参照してください。
ディレクトリの先頭に
.
文字があると、TypeScript で 問題 が起きるため、例えば.well-known
のようなルート(route)を作る場合はこれらの文字をエンコードしておくと良いでしょう:src/routes/[x+2e]well-known/...
Advanced layouts
デフォルトでは、 レイアウトの階層 が ルート(route)の階層 に反映されます。場合によっては、そうしたくないこともあるかもしれません。
(group)
‘アプリ’ のルート(routes)としてのレイアウト (例えば /dashboard
や /item
) が1つあり、’マーケティング’ のルート(routes)としての別のレイアウト (/about
や /testimonials
) があるかもしれません。これらのルート(routes)を、ディレクトリの名前を括弧でくくることでグループ化することができます。通常のディレクトリとは異なり、(app)
や (marketing)
はそれらの中のルート(routes)の URL パス名には影響しません:
src/routes/
│ (app)/
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
│ (marketing)/
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte
+page
を (group)
の中に直接配置することもできます (例えば、/
が (app)
や (marketing)
のページであるべき場合など)。
Breaking out of layouts
最上位のレイアウト(root layout)は、アプリの全てのページに適用されます。省略した場合、デフォルトは {@render children()}
です。もし、いくつかのページで他のページとは異なるレイアウト階層を持ちたい場合には、アプリ全体を1つまたは複数のグループにして、共通のレイアウトを継承しないルート(route)を分けることができます。
上記の例で、/admin
ルート(route)は (app)
や (marketing)
のレイアウトを継承しません。
+page@
ページは、ルート(route)ごとに現在のレイアウト階層から抜け出すことができます。先ほどの例に出てきた (app)
グループの中に、/item/[id]/embed
ルート(route)があるとします:
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
通常、これは最上位のレイアウト(root layout)と (app)
レイアウトと item
レイアウトと [id]
レイアウトを継承します。@
と、その後ろにセグメント名 (最上位のレイアウト(root layout)の場合は空文字列(empty string)) を追加することで、これらのレイアウトのどれかにリセットすることができます。この例では、以下のオプションから選択できます:
+page@[id].svelte
-src/routes/(app)/item/[id]/+layout.svelte
を継承します+page@item.svelte
-src/routes/(app)/item/+layout.svelte
を継承します+page@(app).svelte
-src/routes/(app)/+layout.svelte
を継承します+page@.svelte
-src/routes/+layout.svelte
を継承します
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page@(app).svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
+layout@
ページと同じように、同じ方法でレイアウト 自体 をその親のレイアウトの階層から外すことができます。例えば、+layout@.svelte
コンポーネントはその全ての子ルート(routes)の階層をリセットします。
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte // uses (app)/item/[id]/+layout.svelte
│ │ │ ├ +layout.svelte // inherits from (app)/item/+layout@.svelte
│ │ │ └ +page.svelte // uses (app)/item/+layout@.svelte
│ │ └ +layout@.svelte // inherits from root layout, skipping (app)/+layout.svelte
│ └ +layout.svelte
└ +layout.svelte
レイアウトグループを使うときは
全てのユースケースがレイアウトのグループ化に適しているわけではありませんし、無理に使用する必要もありません。あなたのユースケースが複雑な (group)
のネストになってしまうかもしれませんし、たった1つの例外ケースのために (group)
を導入したくないかもしれません。コンポジション (再利用可能な load
関数や Svelte コンポーネント) や if 文など、他の手段を使用してやりたいことを実現するのは全く問題ありません。以下の例では、最上位のレイアウト(root layout)に戻し、他のレイアウトでも使用できるコンポーネントや関数を再利用したレイアウトを示しています:
<script>
import ReusableLayout from '$lib/ReusableLayout.svelte';
let { data, children } = $props();
</script>
<ReusableLayout {data}>
{@render children()}
</ReusableLayout>
import { function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad } from '$lib/reusable-load-function';
/** @type {import('./$types').PageLoad} */
export function function load(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>
load(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event) {
// Add additional logic here, if needed
return function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event);
}
import { function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad } from '$lib/reusable-load-function';
import type { type PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const load: PageLoad
load: type PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = (event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event) => {
// Add additional logic here, if needed
return function reusableLoad(event: import("@sveltejs/kit").LoadEvent): Promise<Record<string, any>>
reusableLoad(event: LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>
event);
};