進階路由
剩餘參數
如果路由區段的數量未知,您可以使用剩餘語法 — 例如,您可能會像這樣實作 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
等等。請確保您檢查剩餘參數的值是否有效,例如使用匹配器。
404 頁面
剩餘參數也允許您渲染自訂的 404 頁面。給定這些路由...
src/routes/
├ marx-brothers/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
...如果您訪問 /marx-brothers/karl
,marx-brothers/+error.svelte
檔案將不會被渲染,因為沒有匹配的路由。如果您想要渲染巢狀錯誤頁面,您應該建立一個匹配任何 /marx-brothers/*
請求的路由,並從中返回 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
中
可選參數
像 [lang]/home
這樣的路由包含一個名為 lang
的參數,這是必需的。有時候,將這些參數設為可選的會很有利,因此在這個範例中,home
和 en/home
都指向同一個頁面。您可以使用另一對括號將參數包起來來做到這一點:[[lang]]/home
請注意,可選的路由參數不能跟在剩餘參數之後 ([...rest]/[[optional]]
),因為參數是「貪婪地」匹配的,而可選參數將永遠不會被使用。
匹配
像 src/routes/fruits/[page]
這樣的路由會匹配 /fruits/apple
,但它也會匹配 /fruits/rocketship
。我們不希望這樣。您可以透過加入匹配器來確保路由參數格式正確 — 該匹配器會接收參數字串 ("apple"
或 "rocketship"
),如果有效則返回 true
— 到您的 params
目錄中...
/**
* @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;
...並增強您的路由
src/routes/fruits/[page=fruit]
如果路徑名稱不匹配,SvelteKit 將會嘗試匹配其他路由 (使用下面指定的排序順序),最後返回 404。
params
目錄中的每個模組都對應到一個匹配器,但 *.test.js
和 *.spec.js
檔案除外,它們可以用於單元測試您的匹配器。
匹配器會在伺服器和瀏覽器上執行。
排序
多個路由有可能會匹配給定的路徑。例如,這些路由中的每一個都會匹配 /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 需要知道正在請求哪個路由。為了做到這一點,它會根據以下規則對它們進行排序...
- 更具體的路由具有更高的優先順序 (例如,沒有參數的路由比具有一個動態參數的路由更具體,依此類推)
- 具有匹配器的參數 (
[name=type]
) 比沒有匹配器的參數 ([name]
) 具有更高的優先順序 [[optional]]
和[...rest]
參數會被忽略,除非它們是路由的最後一部分,在這種情況下,它們會以最低優先順序處理。換句話說,就排序而言,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
編碼
某些字元不能在檔案系統上使用 — Linux 和 Mac 上的 /
,以及 Windows 上的 \ / : * ? " < > |
。#
和 %
字元在 URL 中具有特殊含義,而 [ ] ( )
字元對 SvelteKit 具有特殊含義,因此這些字元也不能直接用作路由的一部分。
若要在您的路由中使用這些字元,您可以使用十六進位跳脫序列,其格式為 [x+nn]
,其中 nn
是十六進位字元碼
\
—[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/:-)
路由,您將會建立一個 src/routes/smileys/[x+3a]-[x+29]/+page.svelte
檔案。
您可以使用 JavaScript 判斷字元的十六進位碼
':'.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 編碼,請參閱 使用 Unicode 程式設計。
由於 TypeScript 在處理以
.
字元開頭的目錄時會遇到困難,因此當建立例如.well-known
路由時,您可能會發現對這些字元進行編碼很有用:src/routes/[x+2e]well-known/...
進階版面配置
預設情況下,版面配置階層會鏡像路由階層。在某些情況下,這可能不是您想要的。
(群組)
也許您有一些應該具有一個版面配置的「應用程式」路由 (例如 /dashboard
或 /item
),以及其他應該具有不同版面配置的「行銷」路由 (/about
或 /testimonials
)。我們可以使用名稱以括號包起來的目錄來分組這些路由 — 與一般目錄不同,(app)
和 (marketing)
不會影響它們內部路由的 URL 路徑名稱
src/routes/
│ (app)/
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
│ (marketing)/
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte
您也可以將 +page
直接放在 (group)
內,例如,如果 /
應該是 (app)
或 (marketing)
頁面。
跳脫版面配置
根版面配置會套用至您應用程式的每個頁面 — 如果省略,則預設為 {@render children()}
。如果您希望某些頁面具有與其餘頁面不同的版面配置階層,那麼您可以將您的整個應用程式放在一個或多個群組中,除了不應繼承通用版面配置的路由之外。
在上面的範例中,/admin
路由不會繼承 (app)
或 (marketing)
版面配置。
+page@
頁面可以根據每個路由跳脫目前的版面配置階層。假設我們在先前範例的 (app)
群組中具有 /item/[id]/embed
路由
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
通常,這會繼承根版面配置、(app)
版面配置、item
版面配置和 [id]
版面配置。我們可以透過附加 @
後面接著區段名稱來重設為其中一個版面配置 — 或者,對於根版面配置,則使用空字串。在這個範例中,我們可以從以下選項中選擇
+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
元件會重設其所有子路由的階層。
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)
巢狀結構,或者您不想為了單一的例外情況而引入 (group)
。完全可以使用其他方法,例如組合 (可重複使用的 load
函式或 Svelte 元件) 或 if 語句來達到您想要的效果。以下範例展示了一個版面配置,它可以回溯到根版面配置,並重複使用其他版面配置也可以使用的元件和函式。
<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);
};