高级路由
Rest 参数(Rest parameters)
¥Rest parameters
如果路由段的数量未知,则可以使用 rest 语法 — 例如,你可以像这样实现 GitHub 的文件查看器...
¥If the number of route segments is unknown, you can use rest syntax — for example you might implement GitHub’s file viewer like so...
/[org]/[repo]/tree/[branch]/[...file]
...在这种情况下,对 /sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md
的请求将导致页面可以使用以下参数:
¥...in which case a request for /sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md
would result in the following parameters being available to the page:
{
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。¥[!NOTE]
src/routes/a/[...rest]/z/+page.svelte
will match/a/z
(i.e. there’s no parameter at all) as well as/a/b/z
and/a/b/c/z
and so on. Make sure you check that the value of the rest parameter is valid, for example using a matcher.
404 页(404 pages)
¥404 pages
Rest 参数还允许你渲染自定义 404。给定这些路由...
¥Rest parameters also allow you to render custom 404s. Given these routes...
src/routes/
├ marx-brothers/
│ ├ chico/
│ ├ harpo/
│ ├ groucho/
│ └ +error.svelte
└ +error.svelte
...如果你访问 /marx-brothers/karl
,则不会渲染 marx-brothers/+error.svelte
文件,因为没有匹配的路由。如果你想渲染嵌套错误页面,你应该创建一个与任何 /marx-brothers/*
请求匹配的路由,并从中返回 404:
¥...the marx-brothers/+error.svelte
file will not be rendered if you visit /marx-brothers/karl
, because no route was matched. If you want to render the nested error page, you should create a route that matches any /marx-brothers/*
request, and return a 404 from it:
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
中¥[!NOTE] If you don’t handle 404 cases, they will appear in
handleError
可选参数(Optional parameters)
¥Optional parameters
像 [lang]/home
这样的路由包含一个名为 lang
的参数,该参数是必需的。有时将这些参数设为可选参数是有益的,因此在此示例中,home
和 en/home
都指向同一页面。你可以通过将参数封装在另一个括号对中来执行此操作:[[lang]]/home
¥A route like [lang]/home
contains a parameter named lang
which is required. Sometimes it’s beneficial to make these parameters optional, so that in this example both home
and en/home
point to the same page. You can do that by wrapping the parameter in another bracket pair: [[lang]]/home
请注意,可选路由参数不能遵循 rest 参数([...rest]/[[optional]]
),因为参数与 ‘greedily’ 匹配,并且可选参数始终未使用。
¥Note that an optional route parameter cannot follow a rest parameter ([...rest]/[[optional]]
), since parameters are matched ‘greedily’ and the optional parameter would always be unused.
匹配(Matching)
¥Matching
像 src/routes/fruits/[page]
这样的路由会匹配 /fruits/apple
,但也会匹配 /fruits/rocketship
。我们不想那样。你可以通过将匹配器(接受参数字符串("apple"
或 "rocketship"
)并在有效时返回 true
)添加到 params
目录来确保路由参数格式正确...
¥A route like src/routes/fruits/[page]
would match /fruits/apple
, but it would also match /fruits/rocketship
. We don’t want that. You can ensure that route parameters are well-formed by adding a matcher — which takes the parameter string ("apple"
or "rocketship"
) and returns true
if it is valid — to your params
directory...
/**
* @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;
...并增强你的路由:
¥...and augmenting your routes:
src/routes/fruits/[page=fruit]
如果路径名不匹配,SvelteKit 将尝试匹配其他路由(使用下面指定的排序顺序),然后最终返回 404。
¥If the pathname doesn’t match, SvelteKit will try to match other routes (using the sort order specified below), before eventually returning a 404.
params
目录中的每个模块都对应一个匹配器,但 *.test.js
和 *.spec.js
文件除外,它们可用于对匹配器进行单元测试。
¥Each module in the params
directory corresponds to a matcher, with the exception of *.test.js
and *.spec.js
files which may be used to unit test your matchers.
匹配器在服务器和浏览器中运行。
¥[!NOTE] Matchers run both on the server and in the browser.
排序(Sorting)
¥Sorting
多个路由可以匹配给定的路径。例如,这些路由中的每一个都将匹配 /foo-abc
:
¥It’s possible for multiple routes to match a given path. For example each of these routes would match /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 需要知道正在请求哪个路由。为此,它会根据以下规则对它们进行排序...
¥SvelteKit needs to know which route is being requested. To do so, it sorts them according to the following rules...
更具体的路由优先级更高(例如,没有参数的路由比具有一个动态参数的路由更具体,依此类推)
¥More specific routes are higher priority (e.g. a route with no parameters is more specific than a route with one dynamic parameter, and so on)
带有 matchers(
[name=type]
)的参数比不带有([name]
)的参数优先级更高¥Parameters with matchers (
[name=type]
) are higher priority than those without ([name]
)除非
[[optional]]
和[...rest]
参数是路由的最后一部分,否则它们将被忽略,在这种情况下,它们的优先级最低。换句话说,在排序时,x/[[y]]/z
与x/z
被视为等效¥
[[optional]]
and[...rest]
parameters are ignored unless they are the final part of the route, in which case they are treated with lowest priority. In other wordsx/[[y]]/z
is treated equivalently tox/z
for the purposes of sorting关系按字母顺序解决
¥Ties are resolved alphabetically
...导致此顺序,意味着 /foo-abc
将调用 src/routes/foo-abc/+page.svelte
,而 /foo-def
将调用 src/routes/foo-[c]/+page.svelte
,而不是不太具体的路由:
¥...resulting in this ordering, meaning that /foo-abc
will invoke src/routes/foo-abc/+page.svelte
, and /foo-def
will invoke src/routes/foo-[c]/+page.svelte
rather than less specific routes:
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)
¥Encoding
某些字符不能在文件系统上使用 - Linux 和 Mac 上的 /
,Windows 上的 \ / : * ? " < > |
。#
和 %
字符在 URL 中具有特殊含义,[ ] ( )
字符对 SvelteKit 具有特殊含义,因此这些也不能直接用作路由的一部分。
¥Some characters can’t be used on the filesystem — /
on Linux and Mac, \ / : * ? " < > |
on Windows. The #
and %
characters have special meaning in URLs, and the [ ] ( )
characters have special meaning to SvelteKit, so these also can’t be used directly as part of your route.
要在路由中使用这些字符,你可以使用十六进制转义序列,其格式为 [x+nn]
,其中 nn
是十六进制字符代码:
¥To use these characters in your routes, you can use hexadecimal escape sequences, which have the format [x+nn]
where nn
is a hexadecimal character code:
\
—[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
文件。
¥For example, to create a /smileys/:-)
route, you would create a src/routes/smileys/[x+3a]-[x+29]/+page.svelte
file.
你可以使用 JavaScript 确定字符的十六进制代码:
¥You can determine the hexadecimal code for a character with 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 转义序列。通常你不需要这样做,因为你可以直接使用未编码的字符,但如果出于某种原因,你不能使用带有表情符号的文件名,那么你可以使用转义字符。换句话说,这些是等效的:
¥You can also use Unicode escape sequences. Generally you won’t need to as you can use the unencoded character directly, but if — for some reason — you can’t have a filename with an emoji in it, for example, then you can use the escaped characters. In other words, these are equivalent:
src/routes/[u+d83e][u+dd2a]/+page.svelte
src/routes/🤪/+page.svelte
Unicode 转义序列的格式为 [u+nnnn]
,其中 nnnn
是 0000
和 10ffff
之间的有效值。(与 JavaScript 字符串转义不同,无需使用代理对来表示 ffff
以上的代码点。)要了解有关 Unicode 编码的更多信息,请查阅 使用 Unicode 编程。
¥The format for a Unicode escape sequence is [u+nnnn]
where nnnn
is a valid value between 0000
and 10ffff
. (Unlike JavaScript string escaping, there’s no need to use surrogate pairs to represent code points above ffff
.) To learn more about Unicode encodings, consult Programming with Unicode.
由于 TypeScript struggles 的目录以
.
字符开头,因此你可能会发现在创建例如.well-known
路由时对这些字符进行编码很有用:src/routes/[x+2e]well-known/...
¥[!NOTE] Since TypeScript struggles with directories with a leading
.
character, you may find it useful to encode these characters when creating e.g..well-known
routes:src/routes/[x+2e]well-known/...
高级布局(Advanced layouts)
¥Advanced layouts
默认情况下,布局层次结构镜像路由层次结构。在某些情况下,这可能不是你想要的。
¥By default, the layout hierarchy mirrors the route hierarchy. In some cases, that might not be what you want.
(group)
也许你有一些 ‘app’ 路由,应该有一个布局(例如 /dashboard
或 /item
),而其他 ‘marketing’ 路由应该有不同的布局(/about
或 /testimonials
)。我们可以将这些路由与名称括在括号中的目录分组 - 与普通目录不同,(app)
和 (marketing)
不会影响其中路由的 URL 路径名:
¥Perhaps you have some routes that are ‘app’ routes that should have one layout (e.g. /dashboard
or /item
), and others that are ‘marketing’ routes that should have a different layout (/about
or /testimonials
). We can group these routes with a directory whose name is wrapped in parentheses — unlike normal directories, (app)
and (marketing)
do not affect the URL pathname of the routes inside them:
src/routes/
│ (app)/
│ ├ dashboard/
│ ├ item/
│ └ +layout.svelte
│ (marketing)/
│ ├ about/
│ ├ testimonials/
│ └ +layout.svelte
├ admin/
└ +layout.svelte
你还可以将 +page
直接放在 (group)
中,例如,如果 /
应该是 (app)
或 (marketing)
页面。
¥You can also put a +page
directly inside a (group)
, for example if /
should be an (app)
or a (marketing)
page.
打破布局(Breaking out of layouts)
¥Breaking out of layouts
根布局适用于应用的每个页面 - 如果省略,则默认为 {@render children()}
。如果你希望某些页面具有与其他页面不同的布局层次结构,那么你可以将整个应用放在一个或多个组中,但不应继承通用布局的路由除外。
¥The root layout applies to every page of your app — if omitted, it defaults to {@render children()}
. If you want some pages to have a different layout hierarchy than the rest, then you can put your entire app inside one or more groups except the routes that should not inherit the common layouts.
在上面的例子中,/admin
路由不会继承 (app)
或 (marketing)
布局。
¥In the example above, the /admin
route does not inherit either the (app)
or (marketing)
layouts.
+page@
页面可以逐个路由地突破当前布局层次结构。假设我们在上一个示例中的 (app)
组内有一个 /item/[id]/embed
路由:
¥Pages can break out of the current layout hierarchy on a route-by-route basis. Suppose we have an /item/[id]/embed
route inside the (app)
group from the previous example:
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page.svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
通常,这将继承根布局、(app)
布局、item
布局和 [id]
布局。我们可以通过附加 @
后跟段名(或者,对于根布局,为空字符串)来重置为其中一个布局。在此示例中,我们可以从以下选项中进行选择:
¥Ordinarily, this would inherit the root layout, the (app)
layout, the item
layout and the [id]
layout. We can reset to one of those layouts by appending @
followed by the segment name — or, for the root layout, the empty string. In this example, we can choose from the following options:
+page@[id].svelte
- 手风琴是否打开?¥
+page@[id].svelte
- inherits fromsrc/routes/(app)/item/[id]/+layout.svelte
+page@item.svelte
- 手风琴是否打开?¥
+page@item.svelte
- inherits fromsrc/routes/(app)/item/+layout.svelte
+page@(app).svelte
- 手风琴是否打开?¥
+page@(app).svelte
- inherits fromsrc/routes/(app)/+layout.svelte
+page@.svelte
- 手风琴是否打开?¥
+page@.svelte
- inherits fromsrc/routes/+layout.svelte
src/routes/
├ (app)/
│ ├ item/
│ │ ├ [id]/
│ │ │ ├ embed/
│ │ │ │ └ +page@(app).svelte
│ │ │ └ +layout.svelte
│ │ └ +layout.svelte
│ └ +layout.svelte
└ +layout.svelte
+layout@
与页面一样,布局本身可以使用相同的技术脱离其父布局层次结构。例如,+layout@.svelte
组件将重置其所有子路由的层次结构。
¥Like pages, layouts can themselves break out of their parent layout hierarchy, using the same technique. For example, a +layout@.svelte
component would reset the hierarchy for all its child 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
何时使用布局组(When to use layout groups)
¥When to use layout groups
并非所有用例都适合布局分组,你也不应该被迫使用它们。可能是你的用例会导致复杂的 (group)
嵌套,或者你不想为单个异常值引入 (group)
。使用其他方法(例如组合(可重复使用的 load
函数或 Svelte 组件)或 if 语句)来实现你想要的效果是完全没问题的。以下示例显示了一个布局,该布局倒回到根布局并重用其他布局也可以使用的组件和功能:
¥Not all use cases are suited for layout grouping, nor should you feel compelled to use them. It might be that your use case would result in complex (group)
nesting, or that you don’t want to introduce a (group)
for a single outlier. It’s perfectly fine to use other means such as composition (reusable load
functions or Svelte components) or if-statements to achieve what you want. The following example shows a layout that rewinds to the root layout and reuses components and functions that other layouts can also use:
<script>
import ReusableLayout from '$lib/ReusableLayout.svelte';
let { data, children } = $props();
</script>
<ReusableLayout {data}>
{@render children()}
</ReusableLayout>
<script lang="ts">
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);
};
进一步阅读(Further reading)
¥Further reading