跳至主要内容

核心概念

路由

在 GitHub 上编辑此页面

SvelteKit 的核心是一个基于文件系统的路由器。应用程序的路由(即用户可以访问的 URL 路径)由代码库中的目录定义

  • src/routes 是根路由
  • src/routes/about 创建一个 /about 路由
  • src/routes/blog/[slug] 创建一个带有参数 slug 的路由,当用户请求诸如 /blog/hello-world 之类的页面时,可以使用该参数动态加载数据

你可以通过编辑项目配置src/routes 更改为不同的目录。

每个路由目录包含一个或多个路由文件,可以通过其 + 前缀来识别。

+page

+page.svelte

+page.svelte 组件定义了应用程序的一个页面。默认情况下,页面在服务器上(SSR)渲染以进行初始请求,并在浏览器中(CSR)渲染以进行后续导航。

src/routes/+page.svelte
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
src/routes/about/+page.svelte
<h1>About this site</h1>
<p>TODO...</p>
<a href="/">Home</a>
src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>

请注意,SvelteKit 使用 <a> 元素在路由之间导航,而不是特定于框架的 <Link> 组件。

+page.js

通常,页面需要在渲染之前加载一些数据。为此,我们添加了一个导出 load 函数的 +page.js 模块

src/routes/blog/[slug]/+page.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
error(404, 'Not found');
}
src/routes/blog/[slug]/+page.ts
ts
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...',
};
}
error(404, 'Not found');
};

此函数与 +page.svelte 并行运行,这意味着它在服务器端渲染期间在服务器上运行,在客户端导航期间在浏览器中运行。有关 API 的完整详细信息,请参阅 load

除了 load 之外,+page.js 还可以导出配置页面行为的值

  • export const prerender = truefalse'auto'
  • export const ssr = truefalse
  • export const csr = truefalse

您可以在 页面选项 中找到有关这些选项的更多信息。

+page.server.js

如果您的 load 函数只能在服务器上运行,例如,如果它需要从数据库中获取数据,或者您需要访问私有 环境变量,如 API 密钥,那么您可以将 +page.js 重命名为 +page.server.js,并将 PageLoad 类型更改为 PageServerLoad

src/routes/blog/[slug]/+page.server.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
const post = await getPostFromDatabase(params.slug);
if (post) {
return post;
}
error(404, 'Not found');
}
src/routes/blog/[slug]/+page.server.ts
ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPostFromDatabase(params.slug);
if (post) {
return post;
}
error(404, 'Not found');
};

在客户端导航期间,SvelteKit 将从服务器加载此数据,这意味着返回的值必须使用 devalue 进行序列化。有关 API 的完整详细信息,请参阅 load

+page.js 一样,+page.server.js 可以导出 页面选项prerenderssrcsr

+page.server.js 文件还可以导出操作。如果 load 允许您从服务器读取数据,则 actions 允许您使用 <form> 元素将数据写入服务器。要了解如何使用它们,请参阅 表单操作 部分。

+error

如果在 load 期间发生错误,SvelteKit 将呈现一个默认错误页面。您可以通过添加 +error.svelte 文件来按路由自定义此错误页面

src/routes/blog/[slug]/+error.svelte
<script>
	import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>
src/routes/blog/[slug]/+error.svelte
<script lang="ts">
	import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>

SvelteKit 会“向上查找”以寻找最近的错误边界——如果上面提到的文件不存在,它将尝试 src/routes/blog/+error.svelte,然后尝试 src/routes/+error.svelte,最后再渲染默认错误页面。如果操作失败(或如果错误是由根 +layoutload 函数抛出的,该函数位于根 +error 的“上方”),SvelteKit 将退出并渲染一个静态回退错误页面,你可以通过创建一个 src/error.html 文件来对其进行自定义。

如果错误发生在 +layout(.server).js 中的 load 函数内部,树中最近的错误边界是该布局上方(而不是旁边)的 +error.svelte 文件。

如果找不到路由(404),将使用 src/routes/+error.svelte(或如果该文件不存在,则使用默认错误页面)。

当错误发生在 handle+server.js 请求处理程序内部时,不会使用 +error.svelte

你可以在此处阅读有关错误处理的更多信息。

+layout

到目前为止,我们一直将页面视为完全独立的组件——在导航时,现有的 +page.svelte 组件将被销毁,一个新组件将取而代之。

但在许多应用中,有些元素应在每个页面上可见,例如顶级导航或页脚。与其在每个 +page.svelte 中重复它们,我们可以在布局中放置它们。

+layout.svelte

要创建一个适用于每个页面的布局,请创建一个名为 src/routes/+layout.svelte 的文件。默认布局(如果你不自己创建,SvelteKit 使用的布局)如下所示...

<slot></slot>

...但我们可以添加我们想要的任何标记、样式和行为。唯一的要求是组件包含页面内容的 <slot>。例如,让我们添加一个导航栏

src/routes/+layout.svelte
<nav>
	<a href="/">Home</a>
	<a href="/about">About</a>
	<a href="/settings">Settings</a>
</nav>

<slot></slot>

如果我们为 //about/settings 创建页面...

src/routes/+page.svelte
<h1>Home</h1>
src/routes/about/+page.svelte
<h1>About</h1>
src/routes/settings/+page.svelte
<h1>Settings</h1>

...导航将始终可见,并且在三个页面之间单击只会替换 <h1>

布局可以嵌套。假设我们不仅仅有一个 /settings 页面,而是有嵌套页面,例如 /settings/profile/settings/notifications,它们具有一个共享子菜单(有关真实示例,请参阅 github.com/settings)。

我们可以创建一个仅适用于 /settings 下方页面的布局(同时继承具有顶级导航的根布局)

src/routes/settings/+layout.svelte
<script>
	/** @type {import('./$types').LayoutData} */
	export let data;
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

<slot></slot>
src/routes/settings/+layout.svelte
<script lang="ts">
	import type { LayoutData } from './$types';
	
	export let data: LayoutData;
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

<slot></slot>

您可以在下一节中查看 +layout.js 示例,了解如何填充 data

默认情况下,每个布局都会继承其上方的布局。有时这不是您想要的 - 在这种情况下,高级布局 可以帮助您。

+layout.js

就像 +page.svelte+page.js 加载数据一样,您的 +layout.svelte 组件可以从 +layout.js 中的 load 函数获取数据。

src/routes/settings/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export function load() {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' }
]
};
}
src/routes/settings/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' },
],
};
};

如果 +layout.js 导出 页面选项prerenderssrcsr — 它们将用作子页面的默认值。

从布局的 load 函数返回的数据也对所有子页面可用

src/routes/settings/profile/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;

	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
src/routes/settings/profile/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
	
	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>

通常,在页面之间导航时布局数据不会改变。SvelteKit 将在必要时智能地重新运行 load 函数。

+layout.server.js

要在服务器上运行布局的 load 函数,请将其移动到 +layout.server.js,并将 LayoutLoad 类型更改为 LayoutServerLoad

+layout.js 一样,+layout.server.js 可以导出 页面选项prerenderssrcsr

+server

除了页面之外,您还可以使用 +server.js 文件定义具有 GETPOSTPATCHPUTDELETEOPTIONSHEAD 等 HTTP 动词的函数,这些函数采用 RequestEvent 参数并返回 Response 对象,来定义路由(有时称为“API 路由”或“端点”)。

例如,我们可以使用 GET 处理程序创建一个 /api/random-number 路由

src/routes/api/random-number/+server.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export function GET({ url }) {
const min = Number(url.searchParams.get('min') ?? '0');
const max = Number(url.searchParams.get('max') ?? '1');
const d = max - min;
if (isNaN(d) || d < 0) {
error(400, 'min and max must be numbers, and min must be less than max');
}
const random = min + Math.random() * d;
return new Response(String(random));
}
src/routes/api/random-number/+server.ts
ts
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = ({ url }) => {
const min = Number(url.searchParams.get('min') ?? '0');
const max = Number(url.searchParams.get('max') ?? '1');
const d = max - min;
if (isNaN(d) || d < 0) {
error(400, 'min and max must be numbers, and min must be less than max');
}
const random = min + Math.random() * d;
return new Response(String(random));
};

Response 的第一个参数可以是 ReadableStream,从而可以流式传输大量数据或创建服务器发送的事件(除非部署到会缓冲响应的平台,如 AWS Lambda)。

你可以使用 @sveltejs/kit 中的 errorredirectjson 方法,以方便起见(但这不是必须的)。

如果抛出错误(error(...) 或意外错误),响应将是错误的 JSON 表示或备用错误页面——可通过 src/error.html 自定义——具体取决于 Accept 头。在这种情况下,+error.svelte 组件不会被渲染。你可以在此处阅读有关错误处理的更多信息。

在创建 OPTIONS 处理程序时,请注意 Vite 将注入 Access-Control-Allow-OriginAccess-Control-Allow-Methods 头——除非你添加它们,否则这些头在生产中不会存在。

接收数据

通过导出 POST/PUT/PATCH/DELETE/OPTIONS/HEAD 处理程序,可以将 +server.js 文件用于创建完整的 API

src/routes/add/+page.svelte
<script>
	let a = 0;
	let b = 0;
	let total = 0;

	async function add() {
		const response = await fetch('/api/add', {
			method: 'POST',
			body: JSON.stringify({ a, b }),
			headers: {
				'content-type': 'application/json'
			}
		});

		total = await response.json();
	}
</script>

<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}

<button on:click={add}>Calculate</button>
src/routes/add/+page.svelte
<script lang="ts">
	let a = 0;
	let b = 0;
	let total = 0;
	
	async function add() {
		const response = await fetch('/api/add', {
			method: 'POST',
			body: JSON.stringify({ a, b }),
			headers: {
				'content-type': 'application/json',
			},
		});
	
		total = await response.json();
	}
</script>

<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}

<button on:click={add}>Calculate</button>
src/routes/api/add/+server.js
ts
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
src/routes/api/add/+server.ts
ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => {
const { a, b } = await request.json();
return json(a + b);
};

通常情况下,表单操作是将数据从浏览器提交到服务器的更好方法。

如果导出了 GET 处理程序,则 HEAD 请求将返回 GET 处理程序响应正文的 content-length

备用方法处理程序

导出 fallback 处理程序将匹配任何未处理的请求方法,包括 MOVE 等没有从 +server.js 专用导出的方法。

src/routes/api/add/+server.js
ts
import { json, text } from '@sveltejs/kit';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// This handler will respond to PUT, PATCH, DELETE, etc.
/** @type {import('./$types').RequestHandler} */
export async function fallback({ request }) {
return text(`I caught your ${request.method} request!`);
}
src/routes/api/add/+server.ts
ts
import { json, text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// This handler will respond to PUT, PATCH, DELETE, etc.
export const fallback: RequestHandler = async ({ request }) => {
return text(`I caught your ${request.method} request!`);
};

对于 HEAD 请求,GET 处理程序优先于 fallback 处理程序。

内容协商

+server.js 文件可以放置在与 +page 文件相同的目录中,从而允许同一路由成为页面或 API 端点。为了确定哪一个,SvelteKit 应用以下规则

  • PUT/PATCH/DELETE/OPTIONS 请求始终由 +server.js 处理,因为它们不适用于页面
  • 如果 accept 头部优先考虑 text/html(换句话说,这是一个浏览器页面请求),则 GET/POST/HEAD 请求将被视为页面请求,否则将由 +server.js 处理。
  • GET 请求的响应将包含一个 Vary: Accept 头部,以便代理和浏览器分别缓存 HTML 和 JSON 响应。

$types

在上述所有示例中,我们一直在从 $types.d.ts 文件导入类型。如果你使用 TypeScript(或带有 JSDoc 类型注释的 JavaScript)来在处理根文件时提供类型安全性,SvelteKit 会在隐藏目录中为你创建一个这样的文件。

例如,使用 PageData(或 LayoutData,用于 +layout.svelte 文件)注释 export let data 会告诉 TypeScript,data 的类型是 load 返回的任何内容

src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

反过来,使用 PageLoadPageServerLoadLayoutLoadLayoutServerLoad(分别用于 +page.js+page.server.js+layout.js+layout.server.js)注释 load 函数可确保 params 和返回值的类型正确。

如果你使用的是 VS Code 或任何支持语言服务器协议和 TypeScript 插件的 IDE,那么你可以完全省略这些类型!Svelte 的 IDE 工具会为你插入正确的类型,这样你就可以在不自己编写类型的情况下获得类型检查。它还可以与我们的命令行工具 svelte-check 一起使用。

你可以在我们的 博客文章 中阅读有关省略 $types 的更多信息。

其他文件

SvelteKit 会忽略路由目录中的任何其他文件。这意味着你可以将组件和实用程序模块与需要它们的路由放在一起。

如果多个路由需要组件和模块,最好将它们放在 $lib 中。

进一步阅读

上一个 Web 标准
下一个 加载数据