This is the developer documentation for SvelteKit. # 介绍 ## 开始之前 > [!NOTE] 如果您是 Svelte 或 SvelteKit 的新手,我们建议查看[交互式教程](/tutorial/kit)。 > > 如果遇到问题,可以在 [Discord 聊天室](/chat)寻求帮助。 ## 什么是 SvelteKit? SvelteKit 是一个使用 [Svelte](../svelte) 快速开发稳健、高性能 Web 应用程序的框架。如果您来自 React,SvelteKit 类似于 Next。如果您来自 Vue,SvelteKit 类似于 Nuxt。 要了解更多关于可以用 SvelteKit 构建的应用程序类型,请参阅[常见问题](faq#What-can-I-make-with-SvelteKit)。 ## 什么是 Svelte? 简而言之,Svelte 是一种编写用户界面组件的方式 —— 比如导航栏、评论区或联系表单 —— 这些组件用户可以在浏览器中看到并与之交互。Svelte 编译器将您的组件转换为可以运行的 JavaScript 来渲染页面的 HTML,以及为页面添加样式 CSS。您不需要了解 Svelte 就能理解本指南的其余部分,但了解它会有所帮助。如果您想了解更多,请查看 [Svelte 教程](/tutorial)。 ## SvelteKit 与 Svelte 的区别 Svelte 负责渲染 UI 组件。您可以组合这些组件并仅使用 Svelte 渲染整个页面,但要构建完整的应用程序,您需要的不仅仅是 Svelte。 SvelteKit 帮助您在遵循现代最佳实践的同时构建 Web 应用,并为常见的开发挑战提供解决方案。它提供从基本功能 —— 比如在点击链接时更新 UI 的[路由器](glossary#Routing) —— 到更高级的功能。 它的广泛功能列表包括:仅加载最小所需代码的[构建优化](https://vitejs.dev/guide/features.html#build-optimizations);[离线支持](service-workers);用户导航前的页面[预加载](link-options#data-sveltekit-preload-data);通过 [SSR](glossary#SSR)、浏览器[客户端渲染](glossary#CSR)或构建时[预渲染](glossary#Prerendering)来处理应用程序不同部分的[可配置渲染](page-options);[图像优化](images);等等。使用所有现代最佳实践构建应用程序非常复杂,但 SvelteKit 为您处理了所有繁琐的工作,这样您就可以专注于创造性的部分。 它利用带有 [Svelte 插件](https://github.com/sveltejs/vite-plugin-svelte)的 [Vite](https://vitejs.dev/) 来实现[热模块替换 (HMR)](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot),从而在浏览器中即时反映代码更改,提供闪电般快速且功能丰富的开发体验。 # 创建项目 创建 SvelteKit 应用最简单的方法是运行 `npx sv create`: ```bash npx sv create my-app cd my-app npm install npm run dev ``` 第一个命令将在 `my-app` 目录中搭建一个新项目,并询问您是否要设置一些基本工具,比如 TypeScript。有关设置其他工具的指导,请参见[集成](./integrations)。随后的命令将安装其依赖项并在 [localhost:5173](http://localhost:5173) 上启动服务端。 这里有两个基本概念: - 应用的每个页面都是一个 [Svelte](../svelte) 组件 - 通过在项目的 `src/routes` 目录中添加文件来创建页面。这些页面将被服务端渲染,以确保用户首次访问您的应用时速度尽可能快,之后客户端应用将接管 尝试编辑文件以了解所有功能是如何工作的。 ## 编辑器设置 我们推荐使用 [Visual Studio Code (简称 VS Code)](https://code.visualstudio.com/download) 并安装 [Svelte 扩展](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode),但[也支持许多其他编辑器](https://sveltesociety.dev/resources#editor-support)。 # 项目结构 一个典型的 SvelteKit 项目结构如下: ```bash my-project/ ├ src/ │ ├ lib/ │ │ ├ server/ │ │ │ └ [您的仅服务端库文件] │ │ └ [您的库文件] │ ├ params/ │ │ └ [您的参数匹配器] │ ├ routes/ │ │ └ [您的路由] │ ├ app.html │ ├ error.html │ ├ hooks.client.js │ ├ hooks.server.js │ └ service-worker.js ├ static/ │ └ [您的静态资源] ├ tests/ │ └ [您的测试] ├ package.json ├ svelte.config.js ├ tsconfig.json └ vite.config.js ``` 您还会发现一些常见文件,如 `.gitignore` 和 `.npmrc`(如果您在运行 `npx sv create` 时选择了这些选项,还会有 `.prettierrc` 和 `eslint.config.js` 等)。 ## 项目文件 ### src `src` 目录包含了项目的主要内容。除了 `src/routes` 和 `src/app.html` 之外,其他都是可选的。 - `lib` 包含您的库代码(实用工具和组件),可以通过 [`$lib`]($lib) 别名导入,或使用 [`svelte-package`](packaging) 打包分发 - `server` 包含您的仅服务端(server-only)库代码。可以使用 [`$lib/server`](server-only-modules) 别名导入。SvelteKit 会阻止您在客户端代码中导入这些内容。 - `params` 包含应用需要的任何[参数匹配器](advanced-routing#Matching) - `routes` 包含应用的[路由](routing)。您也可以在这里放置仅在单个路由中使用的其他组件 - `app.html` 是您的页面模板 — 一个包含以下占位符的 HTML 文档: - `%sveltekit.head%` — 应用需要的 `` 和 `

{data.title}

{@html data.content}
``` > [!遗留模式] > 在 Svelte 4 中,您需要使用 `export let data` 代替 > [!NOTE] SvelteKit 使用 `` 元素在路由之间导航,而不是框架特定的 `` 组件。 ### +page.js 通常,页面在渲染之前需要加载一些数据。为此,我们添加一个 `+page.js` 模块,该模块导出一个 `load` 函数: ```js /// file: src/routes/blog/[slug]/+page.js 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'); } ``` 这个函数与 `+page.svelte` 一起运行,这意味着它在服器端渲染期间在服务端上运行,在客户端导航期间在浏览器中运行。有关该 API 的完整详细信息,请参见 [`load`](load)。 除了 `load`,`+page.js` 还可以导出一些值用于配置页面行为: - `export const prerender = true` 或 `false` 或 `'auto'` - `export const ssr = true` 或 `false` - `export const csr = true` 或 `false` 您可以在[页面选项](page-options)中找到更多相关信息。 ### +page.server.js 如果您的 `load` 函数只能在服务端上运行(例如,如果它需要从数据库获取数据或需要访问私有[环境变量]($env-static-private),如 API 密钥),那么您可以将 `+page.js` 重命名为 `+page.server.js`,并将 `PageLoad` 类型更改为 `PageServerLoad`。 ```js /// file: src/routes/blog/[slug]/+page.server.js // @filename: ambient.d.ts declare global { const getPostFromDatabase: (slug: string) => { title: string; content: string; } } export {}; // @filename: index.js // ---cut--- 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'); } ``` 在客户端导航期间,SvelteKit 将从服务端加载此数据,这意味着返回值必须使用 [devalue](https://github.com/rich-harris/devalue) 进行序列化。有关该 API 的完整详细信息,请参见 [`load`](load)。 与 `+page.js` 类似,`+page.server.js` 可以导出页面选项 — `prerender`、`ssr` 和 `csr`。 `+page.server.js` 文件还可以导出 _actions_。如果 `load` 让您从服务端读取数据,那么 `actions` 让您使用 `
` 元素向服务端写入数据。要了解如何使用它们,请参阅 [form actions](form-actions) 章节。 ## +error 如果在 `load` 期间发生错误,SvelteKit 将渲染默认错误页面。您可以通过添加 `+error.svelte` 文件来自定义每个路由的错误页面: ```svelte

{page.status}: {page.error.message}

``` > [!LEGACY] > `$app/state` 是在 SvelteKit 2.12 中添加的。如果你使用的是早期版本或正在使用 Svelte 4,请改用 `$app/stores`。 SvelteKit 会"向上遍历"寻找最近的错误边界 —— 如果上面的文件不存在,它会尝试 `src/routes/blog/+error.svelte` 然后是 `src/routes/+error.svelte`,之后才会渲染默认错误页面。如果失败(或者如果错误是从根 `+layout` 的 `load` 函数抛出的,该函数位于根 `+error` 之上),SvelteKit 将退出并渲染一个静态的后备错误页面,你可以通过创建 `src/error.html` 文件来自定义它。 如果错误发生在 `+layout(.server).js` 中的 `load` 函数内,树中最近的错误边界是该布局上方的 `+error.svelte` 文件(而不是在其旁边)。 如果找不到路由(404),将使用 `src/routes/+error.svelte`(或者如果该文件不存在,则使用默认错误页面)。 > [!NOTE] 当错误发生在 [`handle`](hooks#Server-hooks-handle) 或 [+server.js](#server) 请求处理程序中时,不会使用 `+error.svelte`。 您可以在[这里](errors)阅读更多关于错误处理的内容。 ## +layout 到目前为止,我们将页面视为完全独立的组件 —— 在导航时,现有的 `+page.svelte` 组件将被销毁,新的组件将取而代之。 但在许多应用中,有些元素应该在每个页面上都可见,比如顶层导航或页脚。与其在每个 `+page.svelte` 中重复它们,我们可以将它们放在布局中。 ### +layout.svelte 要创建一个适用于每个页面的布局,创建一个名为 `src/routes/+layout.svelte` 的文件。默认布局(即当你没有提供自己的布局时 SvelteKit 使用的布局)看起来是这样的... ```svelte {@render children()} ``` ...但我们可以添加任何想要的标记、样式和行为。唯一的要求是组件必须包含一个用于页面内容的 `@render` 标签。例如,让我们添加一个导航栏: ```svelte
{@render children()} ``` 如果我们为 `/`、`/about` 和 `/settings` 创建页面... ```html /// file: src/routes/+page.svelte

Home

``` ```html /// file: src/routes/about/+page.svelte

About

``` ```html /// file: src/routes/settings/+page.svelte

Settings

``` ...导航栏将始终可见,在这三个页面之间点击只会导致 `

` 被替换。 布局可以嵌套。假设我们不仅有一个 `/settings` 页面,还有像 `/settings/profile` 和 `/settings/notifications` 这样的嵌套页面,它们共享一个子菜单(实际示例请参见 [github.com/settings](https://github.com/settings))。 We can create a layout that only applies to pages below `/settings` (while inheriting the root layout with the top-level nav): 我们可以创建一个仅用于 `/settings` 下方页面的布局(同时继承带有顶级导航的根布局): ```svelte

Settings

{@render children()} ``` 你可以通过查看下方下一节中的 `+layout.js` 示例来了解如何填充 `data`。 默认情况下,每个布局都会继承其上层布局。有时这并不是你想要的 - 在这种情况下,[高级布局](advanced-routing#Advanced-layouts)可以帮助你。 ### +layout.js 就像 `+page.svelte` 从 `+page.js` 加载数据一样,你的 `+layout.svelte` 组件可以从 `+layout.js` 中的 `load` 函数获取数据。 ```js /// file: src/routes/settings/+layout.js /** @type {import('./$types').LayoutLoad} */ export function load() { return { sections: [ { slug: 'profile', title: 'Profile' }, { slug: 'notifications', title: 'Notifications' } ] }; } ``` 如果 `+layout.js` 导出[页面选项](page-options) - `prerender`、`ssr`和 `csr` - 它们将用作子页面的默认值。 布局的 `load` 函数返回的数据也可用于其所有子页面: ```svelte ``` > [!NOTE] 通常,在页面之间导航时布局数据保持不变。SvelteKit 会在必要时智能地重新运行 [`load`](load) 函数。 ### +layout.server.js 要在服务端上运行布局的 `load` 函数,将其移至 `+layout.server.js`,并将 `LayoutLoad` 类型更改为 `LayoutServerLoad`。 与 `+layout.js` 一样,`+layout.server.js` 可以导出[页面选项](page-options) — `prerender`, `ssr` and `csr`. ## +server 除了页面之外,你还可以使用 `+server.js` 文件(有时称为"API 路由"或"端点")定义路由,这使你可以完全控制响应。你的 `+server.js` 文件导出对应 HTTP 动词的函数,如 `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `OPTIONS` 和 `HEAD`,它们接受一个 `RequestEvent` 参数并返回一个 [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) 对象。 例如,我们可以创建一个 `/api/random-number` 路由,带有一个 `GET` 处理程序: ```js /// file: src/routes/api/random-number/+server.js 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)); } ``` `Response` 的第一个参数可以是 [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream),这使得可以流式传输大量数据或创建 server-sent events(除非部署到像 AWS Lambda 这样会缓冲响应的平台)。 为了方便起见,你可以使用来自 `@sveltejs/kit` 的 `error`、`redirect` 和 `json` 方法(但这不是必需的)。 如果抛出错误(无论是 `error(...)` 还是意外错误),响应将是一个错误的 JSON 格式或后备错误页面(可以通过 `src/error.html` 自定义),具体取决于 `Accept` 头部。在这种情况下,[`+error.svelte`](#error) 组件将不会被渲染。你可以在[这里](errors)阅读更多关于错误处理的信息。 > [!NOTE] 创建 `OPTIONS` 处理程序时,请注意 `Vite` 将注入`Access-Control-Allow-Origin` 和 `Access-Control-Allow-Methods` 头部 — 除非你添加它们,否则这些头部在生产环境中不会出现。 > [!NOTE] `+layout` 文件对 `+server.js` 文件没有影响。如果你想在每个请求之前运行一些逻辑,请将其添加到服务端 [`handle`](hooks#Server-hooks-handle) hook 中。 ### 接收数据 通过导出 `POST`/`PUT`/`PATCH`/`DELETE`/`OPTIONS`/`HEAD` 处理程序,`+server.js` 文件可用于创建完整的 API: ```svelte + = {total} ``` ```js /// file: src/routes/api/add/+server.js import { json } from '@sveltejs/kit'; /** @type {import('./$types').RequestHandler} */ export async function POST({ request }) { const { a, b } = await request.json(); return json(a + b); } ``` > [!NOTE] 一般来说,[form actions](form-actions) 是从浏览器向服务端提交数据的更好方式。 > [!NOTE] 如果导出了 `GET` 处理程序,`HEAD` 请求将返回 `GET` 处理程序响应体的`content-length`。 ### 后备方法处理程序 导出 `fallback` 处理程序将匹配任何未处理的请求方法,包括像 `MOVE` 这样没有从 `+server.js` 专门导出的方法。 ```js // @errors: 7031 /// file: src/routes/api/add/+server.js 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!`); } ``` > [!NOTE] 对于 `HEAD` 请求,`GET` 处理程序优先于 `fallback` 处理程序。 ### 内容协商 `+server.js` 文件可以与 `+page` 文件放在同一目录中,使同一路由既可以是页面也可以是 API 端点。为了确定是哪一种,SvelteKit 应用以下规则: - `PUT`/`PATCH`/`DELETE`/`OPTIONS` 请求总是由 `+server.js` 处理,因为它们不适用于页面 - `GET` / `POST` /`HEAD` 请求在 `accept` 头优先考虑 `text/html` 时被视为页面请求(换句话说,这是浏览器的页面请求),否则由 `+server.js` 处理。 - 对 `GET` 请求的响应将包含 `Vary: Accept` 标头,以便代理和浏览器分别缓存 HTML 和 JSON 响应。 ## $types 在上述所有示例中,我们一直在从 `$types.d.ts` 文件导入类型。如果您使用 TypeScript(或带有 JSDoc 类型注释的 JavaScript),SvelteKit 会在隐藏目录中为您创建这个文件,以便在处理根文件时提供类型安全。 例如,用 `PageData`(或者对于 `+layout.svelte` 文件使用 `LayoutData`)注释 `let { data } = $props()` 告诉 TypeScript,`data` 的类型就是从 `load` 返回的类型: ```svelte ``` 反过来,使用 `load` 函数并用 `PageLoad`、`PageServerLoad`、`LayoutLoad` 或 `LayoutServerLoad`(分别对应 `+page.js`、`+page.server.js`、`+layout.js` 和 `+layout.server.js`)进行注解,可以确保 `params` 和返回值被正确类型化。 如果你使用 VS Code 或任何支持语言服务协议和 TypeScript 插件的 IDE,那么你可以完全省略这些类型!Svelte 的 IDE 工具会为你插入正确的类型,所以你无需自己编写就能获得类型检查。它也可以与我们的命令行工具 `svelte-check` 一起使用。 你可以在我们关于省略 `$types` 的[博客文章](/blog/zero-config-type-safety)中了解更多信息。 ## 其他文件 Any other files inside a route directory are ignored by SvelteKit. This means you can colocate components and utility modules with the routes that need them. SvelteKit 会忽略路由目录中的任何其他文件。这意味着你可以将组件和工具模块与需要它们的路由放在一起。 If components and modules are needed by multiple routes, it's a good idea to put them in [`$lib`]($lib). 如果多个路由都需要这些组件和模块,最好将它们放在 [`$lib`]($lib) 中。 ## Further reading - [教程:路由](/tutorial/kit/pages) - [教程:API 路由](/tutorial/kit/get-handlers) - [文档:高级路由](advanced-routing) # 数据加载 在渲染一个 [`+page.svelte`](routing#page-page.svelte) 组件(及其包含的 [`+layout.svelte`](routing#layout-layout.svelte) 组件)之前,我们通常需要获取一些数据。这是通过定义 `load` 函数来实现的。 ## 页面数据 一个 `+page.svelte` 文件可以有一个同级的 `+page.js` 文件,该文件导出一个 `load` 函数,该函数的返回值可以通过 `data` 属性在页面中使用: ```js /// file: src/routes/blog/[slug]/+page.js /** @type {import('./$types').PageLoad} */ export function load({ params }) { return { post: { title: `Title for ${params.slug} goes here`, content: `Content for ${params.slug} goes here` } }; } ``` ```svelte

{data.post.title}

{@html data.post.content}
``` > [!LEGACY] > 在 Svelte 4 中,您需要使用 `export let data` 代替 得益于生成的 `$types` 模块,我们获得了完整的类型安全性。 `+page.js` 文件中的 `load` 函数在服务端和浏览器上都会运行(除非与 `export const ssr = false` 结合使用,在这种情况下它将[仅在浏览器中运行](page-options#ssr))。如果您的 `load` 函数应该始终在服务端上运行(例如,因为它使用了私有环境变量或访问数据库),那么它应该放在 `+page.server.js` 中。 一个更贴合实际的博客文章 `load` 函数示例,它只在服务端上运行并从数据库中获取数据。可能如下所示: ```js /// file: src/routes/blog/[slug]/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function getPost(slug: string): Promise<{ title: string, content: string }> } // @filename: index.js // ---cut--- import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { post: await db.getPost(params.slug) }; } ``` 注意类型从 `PageLoad` 变为 `PageServerLoad`,因为服务端 `load` 函数可以访问额外的参数。要了解何时使用 `+page.js` 和何时使用 `+page.server.js`文档:高级路由 请参阅[Universal 与 server](load#Universal-vs-server)。 ## 布局数据 您的 `+layout.svelte` 文件也可以通过 `+layout.js` 或 `+layout.server.js` 加载数据。 ```js /// file: src/routes/blog/[slug]/+layout.server.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function getPostSummaries(): Promise> } // @filename: index.js // ---cut--- import * as db from '$lib/server/database'; /** @type {import('./$types').LayoutServerLoad} */ export async function load() { return { posts: await db.getPostSummaries() }; } ``` ```svelte
{@render children()}
``` 布局 `load` 函数返回的数据对子 `+layout.svelte` 组件和 `+page.svelte` 组件以及它"所属"的布局都可用。 ```svelte /// file: src/routes/blog/[slug]/+page.svelte

{data.post.title}

{@html data.post.content}
+++{#if next}

Next post: {next.title}

{/if}+++ ``` > [!NOTE] 如果多个 `load` 函数返回具有相同键的数据,最后一个会"胜出" —— 布局 `load` 返回 `{ a: 1, b: 2 }` 而页面 `load` 返回 `{ b: 3, c: 4 }` 的结果将是 `{ a: 1, b: 3, c: 4 }`。 ## page.data `+page.svelte` 组件及其上面的每个 `+layout.svelte` 组件都可以访问自己的数据以及其所有父组件的数据。 在某些情况下,我们可能需要相反的效果 - 父布局可能需要访问页面数据或来自子布局的数据。例如,根布局可能想要访问从 `+page.js` 或 `+page.server.js` 中的 `load` 函数返回的 `title` 属性。这可以通过 `page.data` 实现: ```svelte {page.data.title} ``` `page.data` 的类型信息由 `App.PageData` 提供。 > [!LEGACY] > `$app/state` 是在 SvelteKit 2.12 中添加的。如果您使用的是早期版本或使用 Svelte 4,请使用 `$app/stores` 代替。它提供了一个具有相同接口的 `page` store,您可以订阅它,例如 `$page.data.title`。 ## Universal vs server 正如我们所见,有两种类型的 `load` 函数: - `+page.js` 和 `+layout.js` 文件导出的在服务端和浏览器上都运行的**通用** `load` 函数 - `+page.server.js` 和 `+layout.server.js` 文件导出的只在服务端运行的**服务端** `load` 函数 从概念上讲,它们是相同的东西,但有一些重要的区别需要注意。 ### 何时运行哪个 load 函数? 服务端 `load` 函数**总是**在服务端上运行。 默认情况下,通用 `load` 函数在用户首次访问页面时在 SSR 期间在服务端上运行。然后它们会在水合过程中再次运行,复用来自 [fetch 请求](#Making-fetch-requests)的任何响应。所有后续调用通用 `load` 函数都发生在浏览器中。您可以通过[页面选项](page-options)自定义该行为。如果您禁用了[服务端渲染](page-options#ssr),您将获得一个 SPA,通用 `load` 函数**始终**在客户端运行。 如果一个路由同时包含通用和服务端 `load` 函数,服务端 `load` 函数会先运行。 除非您[预渲染](page-options#prerender)页面 - 在这种情况下,它会在构建时被调用,否则 `load` 函数会在运行时被调用。 ### 输入 通用和服务端 `load` 函数都可以访问描述请求的属性(`params`、`route` 和 `url`)以及各种函数(`fetch`、`setHeaders`、`parent`、`depends` 和 `untrack`)。这些在后面的章节中会描述。 服务端 `load` 函数使用 `ServerLoadEvent` 调用,它从 `RequestEvent` 继承 `clientAddress`、`cookies`、`locals`、`platform` 和 `request`。 通用 `load` 函数使用具有 `data` 属性的 `LoadEvent` 调用。如果您在 `+page.js` 和 `+page.server.js`(或 `+layout.js` 和 `+layout.server.js`)中都有 `load` 函数,则服务端 `load` 函数的返回值是通用 `load` 函数参数的 `data` 属性。 ### 输出 通用 `load` 函数可以返回包含任何值的对象,包括自定义类和组件构造函数等内容。 服务端 `load` 函数必须返回可以用 [devalue](https://github.com/rich-harris/devalue) 序列化的数据 - 任何可以用 JSON 表示的内容,以及像 `BigInt`、`Date`、`Map`、`Set` 和 `RegExp` 这样的内容,或重复/循环引用 - 这样它才能通过网络传输。您的数据可以包含[promises](#Streaming-with-promises),在这种情况下它将被流式传输到浏览器。 ### 何时使用哪个 当您需要直接访问数据库或文件系统,或需要使用私有环境变量时,服务端 `load` 函数很方便。 当您需要从外部 API `fetch` 数据且不需要私有凭据时,通用 `load` 函数很有用,因为 SvelteKit 可以直接从 API 获取数据而无需通过服务端。当您需要返回无法序列化的内容(如 Svelte 组件构造函数)时,它们也很有用。 在极少数情况下,您可能需要同时使用两者 - 例如,您可能需要返回一个使用服务端数据初始化的自定义类的实例。当同时使用两者时,服务端 `load` 的返回值**不会**直接传递给页面,而是传递给通用 `load` 函数(作为 `data` 属性): ```js /// file: src/routes/+page.server.js /** @type {import('./$types').PageServerLoad} */ export async function load() { return { serverMessage: 'hello from server load function' }; } ``` ```js /// file: src/routes/+page.js // @errors: 18047 /** @type {import('./$types').PageLoad} */ export async function load({ data }) { return { serverMessage: data.serverMessage, universalMessage: 'hello from universal load function' }; } ``` ## 使用 URL 数据 通常 `load` 函数以某种方式依赖于 URL。为此,`load` 函数提供了 `url`、`route` 和 `params`。 ### url [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) 的一个实例,包含诸如 `origin`、`hostname`、`pathname` 和 `searchParams`(包含解析后的查询字符串,作为 [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) 对象)等属性。在 `load` 期间无法访问 `url.hash`,因为它在服务端上不可用。 > [!NOTE] 在某些环境中,这是在服务端渲染期间从请求头派生的。例如,如果您使用 [adapter-node](adapter-node),您可能需要配置适配器以使 URL 正确。 ### route 包含当前路由目录相对于 `src/routes` 的名称: ```js /// file: src/routes/a/[b]/[...c]/+page.js /** @type {import('./$types').PageLoad} */ export function load({ route }) { console.log(route.id); // '/a/[b]/[...c]' } ``` ### params `params` 是从 `url.pathname` 和 `route.id` 派生的。 给定一个 `route.id` 为 `/a/[b]/[...c]` 且 `url.pathname` 为 `/a/x/y/z` 时,`params` 对象将如下所示: ```json { "b": "x", "c": "y/z" } ``` ## 发起 fetch 请求 要从外部 API 或 `+server.js` 处理程序获取数据,您可以使用提供的 `fetch` 函数,它的行为与[原生 `fetch` web API](https://developer.mozilla.org/en-US/docs/Web/API/fetch)完全相同,但有一些额外的功能: - 它可以在服务端上发起带凭据的请求,因为它继承了页面请求的 `cookie` 和 `authorization` 标头。 - 它可以在服务端上发起相对请求(通常,当在服务端上下文中使用时,`fetch` 需要带有源的 URL)。 - 内部请求(例如对 `+server.js` 路由的请求)在服务端上运行时直接转到处理函数,无需 HTTP 调用的开销。 - 在服务端渲染期间,通过钩入 `text`、`json` 和 `arrayBuffer` 方法来捕获响应并将其内联到渲染的 HTML 中 Response 对象。请注意,除非通过 [`filterSerializedResponseHeaders`](hooks#Server-hooks-handle) 显式包含,否则标头将不会被序列化。 - 在水合过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果在使用浏览器 `fetch` 而不是 `loadfetch` 时,在浏览器控制台中收到警告,这就是原因。 ```js /// file: src/routes/items/[id]/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ fetch, params }) { const res = await fetch(`/api/items/${params.id}`); const item = await res.json(); return { item }; } ``` ## Cookies 服务端 `load` 函数可以获取和设置[`cookies`](@sveltejs-kit#Cookies)。 ```js /// file: src/routes/+layout.server.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function getUser(sessionid: string | undefined): Promise<{ name: string, avatar: string }> } // @filename: index.js // ---cut--- import * as db from '$lib/server/database'; /** @type {import('./$types').LayoutServerLoad} */ export async function load({ cookies }) { const sessionid = cookies.get('sessionid'); return { user: await db.getUser(sessionid) }; } ``` 只有当目标主机与 SvelteKit 应用程序相同或是其更具体的子域名时,Cookie 才会通过提供的 `fetch` 函数传递。 例如,如果 SvelteKit 正在为 my.domain.com 提供服务: - domain.com 将不会接收 cookies - my.domain.com 将会接收 cookies - api.domain.com 将不会接收 cookies - sub.my.domain.com 将会接收 cookies 当设置 `credentials: 'include'` 时,其他 cookies 将不会被传递,因为 SvelteKit 无法知道哪个 cookie 属于哪个域(浏览器不会传递这些信息),所以转发任何 cookie 都是不安全的。使用 [handleFetch hook](hooks#Server-hooks-handleFetch) 钩子来解决这个问题。 ## Headers 服务端和通用 `load` 函数都可以访问 `setHeaders` 函数,当在服务端上运行时,可以为响应设置头部信息。(在浏览器中运行时,setHeaders 不会产生效果。)这在你想要缓存页面时很有用,例如: ```js // @errors: 2322 1360 /// file: src/routes/products/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ fetch, setHeaders }) { const url = `https://cms.example.com/products.json`; const response = await fetch(url); // Headers are only set during SSR, caching the page's HTML // for the same length of time as the underlying data. setHeaders({ age: response.headers.get('age'), 'cache-control': response.headers.get('cache-control') }); return response.json(); } ``` 多次设置相同的标头(即使在不同的 `load` 函数中)是一个错误。使用 `setHeaders` 函数时,每个标头只能设置一次。你不能使用 `setHeaders` 添加 `set-cookie` 标头 — 应该使用`cookies.set(name, value, options)` 代替。 ## 使用父级数据 有时候让 `load` 函数访问父级 `load` 函数中的数据是很有用的,这可以通过 `await parent()` 实现: ```js /// file: src/routes/+layout.js /** @type {import('./$types').LayoutLoad} */ export function load() { return { a: 1 }; } ``` ```js /// file: src/routes/abc/+layout.js /** @type {import('./$types').LayoutLoad} */ export async function load({ parent }) { const { a } = await parent(); return { b: a + 1 }; } ``` ```js /// file: src/routes/abc/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ parent }) { const { a, b } = await parent(); return { c: a + b }; } ``` ```svelte

{data.a} + {data.b} = {data.c}

``` > [!NOTE] 注意,`+page.js` 中的 `load` 函数接收来自两个布局 `load` 函数的合并数据,而不仅仅是直接父级的数据。 在 `+page.server.js` 和 `+layout.server.js` 内部,`parent` 从父级 `+layout.server.js` 文件返回数据。 在 `+page.js` 或 `+layout.js` 中,它将返回父级`+layout.js` 文件中的数据。然而,缺失的 `+layout.js` 会被视为 `({ data }) => data` 函数,这意味着它也会返回未被 `+layout.js` 文件"遮蔽"的父级 `+layout.server.js` 文件中的数据。 使用 `await parent()` 时要注意避免瀑布流。例如,`getData(params)` 并不依赖于调用 `parent()` 的结果,所以我们应该先调用它以避免延迟渲染。 ```js /// file: +page.js // @filename: ambient.d.ts declare function getData(params: Record): Promise<{ meta: any }> // @filename: index.js // ---cut--- /** @type {import('./$types').PageLoad} */ export async function load({ params, parent }) { ---const parentData = await parent();--- const data = await getData(params); +++const parentData = await parent();+++ return { ...data, meta: { ...parentData.meta, ...data.meta } }; } ``` ## Errors 如果在 `load` 期间抛出错误,将渲染最近的 [`+error.svelte`](routing#error)。对于[预期的](errors#Expected-errors)错误,使用来自 `@sveltejs/kit` 的 `error` 辅助函数来指定 HTTP 状态码和可选消息: ```js /// file: src/routes/admin/+layout.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user?: { name: string; isAdmin: boolean; } } } // @filename: index.js // ---cut--- import { error } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ locals }) { if (!locals.user) { error(401, 'not logged in'); } if (!locals.user.isAdmin) { error(403, 'not an admin'); } } ``` 调用 `error(...)` 将抛出一个异常,这使得在辅助函数内部停止执行变得容易。 如果抛出了一个[_意外_](errors#Unexpected-errors)错误,SvelteKit 将调用 [`handleError`](hooks#Shared-hooks-handleError) 并将其视为 500 内部错误。 > [!NOTE] 在 [SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) 中,你必须自己 `throw` 错误 ## Redirects 要重定向用户,请使用来自 `@sveltejs/kit` 的 `redirect` 辅助函数,以指定用户应被重定向到的位置以及一个 `3xx` 状态码。与 `error(...)` 类似,调用 `redirect(...)` 将抛出一个异常,这使得在辅助函数内部停止执行变得容易。 ```js /// file: src/routes/user/+layout.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user?: { name: string; } } } // @filename: index.js // ---cut--- import { redirect } from '@sveltejs/kit'; /** @type {import('./$types').LayoutServerLoad} */ export function load({ locals }) { if (!locals.user) { redirect(307, '/login'); } } ``` > [!NOTE] 不要在 `try {...}` 块内使用 `redirect()`,因为重定向会立即触发 catch 语句。 在浏览器中,你也可以在 `load` 函数之外使用来自 `$app.navigation` 的 `goto` 通过编程的方式进行导航。 > [!NOTE] 在 [SvelteKit 1.x](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you) 中,你必须自己 `throw` 这个 `redirect` ## Streaming with promises 当使用服务端 `load` 时,Promise 将在 resolve 时流式传输到浏览器。如果你有较慢的、非必要的数据,这很有用,因为你可以在所有数据可用之前开始渲染页面: ```js /// file: src/routes/blog/[slug]/+page.server.js // @filename: ambient.d.ts declare global { const loadPost: (slug: string) => Promise<{ title: string, content: string }>; const loadComments: (slug: string) => Promise<{ content: string }>; } export {}; // @filename: index.js // ---cut--- /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { // make sure the `await` happens at the end, otherwise we // can't start loading comments until we've loaded the post comments: loadComments(params.slug), post: await loadPost(params.slug) }; } ``` 这对创建骨架加载状态很有用,例如: ```svelte

{data.post.title}

{@html data.post.content}
{#await data.comments} Loading comments... {:then comments} {#each comments as comment}

{comment.content}

{/each} {:catch error}

error loading comments: {error.message}

{/await} ``` 在流式传输数据时,请注意正确处理 Promise rejections。具体来说,如果懒加载的 Promise 在渲染开始前失败(此时会被捕获)且没有以某种方式处理错误,服务器可能会因 "unhandled promise rejection" 错误而崩溃。 当在 `load` 函数中直接使用 SvelteKit 的 `fetch` 时,SvelteKit 会为您处理这种情况。对于其他 Promise,只需为 Promise 添加一个空的 `catch` 即可将其标记为已处理。 ```js /// file: src/routes/+page.server.js /** @type {import('./$types').PageServerLoad} */ export function load({ fetch }) { const ok_manual = Promise.reject(); ok_manual.catch(() => {}); return { ok_manual, ok_fetch: fetch('/fetch/that/could/fail'), dangerous_unhandled: Promise.reject() }; } ``` > [!NOTE] 在不支持流式传输的平台上(如 AWS Lambda 或 Firebase),响应将被缓冲。这意味着页面只会在所有 promise resolve 后才会渲染。如果您使用代理(例如 NGINX),请确保它不会缓冲来自代理服务器的响应。 > [!NOTE] 流式数据传输只有在启用 JavaScript 时才能工作。如果页面是服务端渲染的,您应该避免从通用 `load` 函数返回 promise,因为这些 promise 不会被流式传输 —— 相反,当函数在浏览器中重新运行时,promise 会被重新创建。 > [!NOTE] 一旦响应开始流式传输,就无法更改响应的标头和状态码,因此您无法 `setHeaders` 或抛出重定向到流式 promise 内。 > [!NOTE] 在 [SvelteKit 1.x](migrating-to-sveltekit-2#Top-level-promises-are-no-longer-awaited) 中,顶层 promise 会自动 awaited,只有嵌套的 promise 才会流式传输。 ## 并行加载 在渲染(或导航到)页面时,SvelteKit 会同时运行所有 `load` 函数,避免请求瀑布。在客户端导航期间,多个服务器 `load` 函数的调用结果会被组合到单个响应中。一旦所有 `load` 函数都返回结果,页面就会被渲染。 ## 重新运行 load 函数 SvelteKit 会追踪每个 `load` 函数的依赖关系,以避免在导航过程中不必要的重新运行。 例如,给定一对这样的 `load` 函数... ```js /// file: src/routes/blog/[slug]/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function getPost(slug: string): Promise<{ title: string, content: string }> } // @filename: index.js // ---cut--- import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { return { post: await db.getPost(params.slug) }; } ``` ```js /// file: src/routes/blog/[slug]/+layout.server.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function getPostSummaries(): Promise> } // @filename: index.js // ---cut--- import * as db from '$lib/server/database'; /** @type {import('./$types').LayoutServerLoad} */ export async function load() { return { posts: await db.getPostSummaries() }; } ``` ...其中 `+page.server.js` 中的函数在从 `/blog/trying-the-raw-meat-diet` 导航到 `/blog/i-regret-my-choices` 时会重新运行,因为 `params.slug` 发生了变化。而 `+layout.server.js` 中的函数则不会重新运行,因为数据仍然有效。换句话说,我们不会第二次调用 `db.getPostSummaries()`。 如果父级 `load` 函数重新运行,调用了 `await parent()` 的 `load` 函数也会重新运行。 依赖追踪在 `load` 函数返回后不再适用 — 例如,在嵌套的 [promise](#Streaming-with-promises) 中访问 `params.x` 不会在 `params.x` 改变时导致函数重新运行。(别担心,如果你不小心这样做了,在开发环境中会收到警告。)相反,应该在 `load` 函数的主体中访问参数。 搜索参数的追踪独立于 URL 的其余部分。例如,在 `load` 函数中访问 `event.url.searchParams.get("x")` 将使该 `load` 函数在从 `?x=1` 导航到 `?x=2` 时重新运行,但从 `?x=1&y=1` 导航到 `?x=1&y=2` 时则不会重新运行。 ### 取消依赖追踪 在极少数情况下,你可能希望将某些内容排除在依赖追踪机制之外。你可以使用提供的 `untrack` 函数实现这一点: ```js /// file: src/routes/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ untrack, url }) { // Untrack url.pathname so that path changes don't trigger a rerun if (untrack(() => url.pathname === '/')) { return { message: 'Welcome!' }; } } ``` ### 手动失效 你还可以使用 [`invalidate(url)`]($app-navigation#invalidate) 重新运行适用于当前页面的 `load` 函数,它会重新运行所有依赖于 `url` 的 `load` 函数,以及使用 [`invalidateAll()`]($app-navigation#invalidateAll) 重新运行每个 `load` 函数。服务端加载函数永远不会自动依赖于获取数据的 `url`,以避免将秘密泄露给客户端。 如果一个 `load` 函数调用了 `fetch(url)` 或 `depends(url)`,那么它就依赖于 `url`。注意,`url` 可以是以 `[a-z]`开头的自定义标识符: ```js /// file: src/routes/random-number/+page.js /** @type {import('./$types').PageLoad} */ export async function load({ fetch, depends }) { // load reruns when `invalidate('https://api.example.com/random-number')` is called... const response = await fetch('https://api.example.com/random-number'); // ...or when `invalidate('app:random')` is called depends('app:random'); return { number: await response.json() }; } ``` ```svelte

random number: {data.number}

``` ### load 函数何时重新运行? 总的来说,`load` 函数在以下情况下会重新运行: - 它引用了 `params` 中已更改值的属性 - 它引用了 `url` 的某个属性(如 `url.pathname` 或`url.search`)且该属性的值已更改。`request.url` 中的属性不会被追踪 - 它调用 `url.searchParams.get(...)`、`url.searchParams.getAll(...)` 或 `url.searchParams.has(...)`,且相关参数发生变化。访问 `url.searchParams` 的其他属性与访问 `url.search`具有相同的效果。 - 它调用 `await parent()` 且父 `load` 函数重新运行 - 当子 `load` 函数调用 `await parent()` 并重新运行,且父函数是服务端 `load` 函数 - 它通过 [`fetch`](#Making-fetch-requests)(仅限通用 load)或 [`depends`](@sveltejs-kit#LoadEvent) 声明了对特定 URL 的依赖,且该 URL 被 [`invalidate(url)`]($app-navigation#invalidate) 标记为无效 - 所有活动的 `load` 函数都被 [`invalidateAll()`]($app-navigation#invalidateAll) 强制重新运行 `params` 和 `url` 可以在响应 `` 链接点击、[`` 交互](form-actions#GET-vs-POST)[`goto`]($app-navigation#goto) 调用或 [`重定向`](@sveltejs-kit#redirect) 时发生变化。 注意,重新运行 `load` 函数将更新相应 `+layout.svelte` 或 `+page.svelte` 中的 `data` 属性;这不会导致组件重新创建。因此,内部状态会被保留。如果这不是你想要的,你可以在[`afterNavigate`]($app-navigation#afterNavigate) 回调中重置所需内容,或者用 [`{#key ...}`](../svelte/key) 块包装你的组件。 ## 对身份验证的影响 数据加载的几个特性对身份验证有重要影响: - 布局 `load` 函数不会在每个请求时运行,例如在子路由之间的客户端导航期间。([load函数何时重新运行?](load#Rerunning-load-functions-When-do-load-functions-rerun)) - 布局和页面 `load` 函数会同时运行,除非调用了 `await parent()`。如果布局 `load` 抛出错误,页面 `load` 函数会运行,但客户端将不会收到返回的数据。 有几种可能的策略来确保在受保护代码之前进行身份验证检查。 为防止数据瀑布并保留布局 `load` 缓存: - 使用 [hooks](hooks) 在任何 `load` 函数运行之前保护多个路由 - 在 `+page.server.js` `load` 函数中直接使用身份验证守卫进行特定路由保护 在 `+layout.server.js` 中放置身份验证守卫要求所有子页面在受保护代码之前调用 `await parent()`。除非每个子页面都依赖于`await parent()` 返回的数据,否则其他选项会更有性能优势。 ## Further reading - [教程:数据加载](/tutorial/kit/page-data) - [教程:错误和重定向](/tutorial/kit/error-basics) - [教程:高级加载](/tutorial/kit/await-parent) # 表单 actions `+page.server.js` 文件可以导出 _actions_,允许您使用 `` 元素向服务端 `POST` 数据。 使用 `` 时,客户端 JavaScript 是可选的,但您可以轻松地使用 JavaScript _渐进式增强_ 表单交互,以提供最佳的用户体验。 ## 默认 action 在最简单的情况下,一个页面声明一个 `default` action: ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO log the user in } }; ``` 要从 `/login` 页面调用此 action,只需添加一个 `` —— 不需要 JavaScript: ```svelte ``` 如果有人点击按钮,浏览器将通过 `POST` 请求将表单数据发送到服务端,运行默认 action。 > [!NOTE] action 总是使用 `POST` 请求,因为 `GET` 请求不应该有副作用。 我们还可以通过添加 `action` 属性,调用来自其他页面的 action (例如,如果根布局中的导航栏有一个登录小部件): ```html /// file: src/routes/+layout.svelte
``` ## 命名 actions 页面可以根据需要拥有多个命名 action ,而不是只有一个 `default` action: ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { --- default: async (event) => {--- +++ login: async (event) => {+++ // TODO log the user in }, +++ register: async (event) => { // TODO register the user }+++ }; ``` 要调用命名 action ,添加一个以 `/` 字符为前缀的查询参数: ```svelte
``` ```svelte ``` 除了 `action` 属性,我们还可以在按钮上使用 `formaction` 属性,将相同的表单数据 `POST` 到与父 `` 不同的 action : ```svelte /// file: src/routes/login/+page.svelte ++++++
``` > [!NOTE] 我们不能在命名 action 旁边有默认 action ,因为如果您在没有重定向的情况下 `POST` 到命名 action ,查询参数会保留在 URL 中,这意味着下一个默认 `POST` 将通过之前的命名 action 进行处理。 ## action 的结构 每个 action 接收一个 `RequestEvent` 对象,允许您使用 `request.formData()` 读取数据。在处理请求之后(例如,通过设置 cookie 让用户登录),action 可以响应数据,这些数据将在对应页面的 `form` 属性以及整个应用范围的 `page.form` 中可用,直到下一次更新。 ```js /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- import * as db from '$lib/server/db'; /** @type {import('./$types').PageServerLoad} */ export async function load({ cookies }) { const user = await db.getUserFromSession(cookies.get('sessionid')); return { user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); const user = await db.getUser(email); cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` ```svelte {#if form?.success}

Successfully logged in! Welcome back, {data.user.name}

{/if} ``` > [!LEGACY] > 在 Svelte 4 中,您将使用 `export let data` 和 `export let form` 来声明属性 ### 验证错误 如果请求因数据无效而无法处理,您可以将验证错误 —— 以及之前提交的表单值 —— 返回给用户,以便他们可以重试。`fail` 函数允许您返回一个 HTTP 状态码(通常是 400 或 422,用于验证错误)以及数据。状态码可以通过 `page.status` 获取,数据可以通过 `form` 获取: ```js /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- +++import { fail } from '@sveltejs/kit';+++ import * as db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); +++ if (!email) { return fail(400, { email, missing: true }); }+++ const user = await db.getUser(email); +++ if (!user || user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); }+++ cookies.set('sessionid', await db.createSession(user), { path: '/' }); return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` > [!NOTE] 请注意,作为预防措施,我们只将电子邮件返回给页面 —— 而不是密码。 ```svelte /// file: src/routes/login/+page.svelte
+++ {#if form?.missing}

邮箱字段为必填项

{/if} {#if form?.incorrect}

凭据无效!

{/if}+++
``` 返回的数据必须可序列化为 JSON。除此之外,结构完全由您决定。例如,如果页面上有多个表单,您可以使用 `id` 属性或类似的方式区分返回的 `form` 数据对应哪个 `
`。 ### 重定向 重定向(和错误)与 [`load`](load#Redirects) 中的工作方式完全相同: ```js // @errors: 2345 /// file: src/routes/login/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/db'; // @filename: index.js // ---cut--- import { fail, +++redirect+++ } from '@sveltejs/kit'; import * as db from '$lib/server/db'; /** @satisfies {import('./$types').Actions} */ export const actions = { login: async ({ cookies, request, +++url+++ }) => { const data = await request.formData(); const email = data.get('email'); const password = data.get('password'); const user = await db.getUser(email); if (!user) { return fail(400, { email, missing: true }); } if (user.password !== db.hash(password)) { return fail(400, { email, incorrect: true }); } cookies.set('sessionid', await db.createSession(user), { path: '/' }); +++ if (url.searchParams.has('redirectTo')) { redirect(303, url.searchParams.get('redirectTo')); }+++ return { success: true }; }, register: async (event) => { // TODO register the user } }; ``` ## 加载数据 action 运行后,页面将重新渲染(除非发生重定向或意外错误), action 的返回值将作为 `form` 属性提供给页面。这意味着页面的 `load` 函数将在 action 完成后运行。 请注意,`handle` 在 action 被调用之前运行,并且不会在 `load` 函数之前重新运行。这意味着,例如,如果您使用 `handle` 根据 cookie 填充 `event.locals`,则在 action 中设置或删除 cookie 时,必须更新 `event.locals`: ```js /// file: src/hooks.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user: { name: string; } | null } } // @filename: global.d.ts declare global { function getUser(sessionid: string | undefined): { name: string; }; } export {}; // @filename: index.js // ---cut--- /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { event.locals.user = await getUser(event.cookies.get('sessionid')); return resolve(event); } ``` ```js /// file: src/routes/account/+page.server.js // @filename: ambient.d.ts declare namespace App { interface Locals { user: { name: string; } | null } } // @filename: index.js // ---cut--- /** @type {import('./$types').PageServerLoad} */ export function load(event) { return { user: event.locals.user }; } /** @satisfies {import('./$types').Actions} */ export const actions = { logout: async (event) => { event.cookies.delete('sessionid', { path: '/' }); event.locals.user = null; } }; ``` ## 渐进式增强 在前面的章节中,我们构建了一个在[没有客户端 JavaScript 的情况下工作](https://kryogenix.org/code/browser/everyonehasjs.html)的 `/login` action —— 没有 `fetch`。这很好,但当 JavaScript _可用_ 时,我们可以渐进式增强表单交互,以提供更好的用户体验。 ### use:enhance 渐进式增强表单的最简单方法是添加 `use:enhance` action : ```svelte /// file: src/routes/login/+page.svelte ``` > [!NOTE] `use:enhance` 只能与 `method="POST"` 的表单一起使用。它将无法与 `method="GET"` 一起工作,后者是未指定方法的表单的默认方法。在未指定 `method="POST"` 的表单上尝试使用 `use:enhance` 将导致错误。 > [!NOTE] 是的,`enhance` action 和 `` 都叫做 'action',这些文档充满了各种 action。抱歉。 没有参数时,`use:enhance` 将模拟浏览器原生行为,只是不进行完整页面重载。它将: - 在成功或无效响应时更新 `form` 属性、`page.form` 和 `page.status`,但仅当 action 在您提交的同一页面上时。例如,如果您的表单看起来像 ``,`form` 属性和 `page.form` 状态将 _不会_ 更新。这是因为在本地表单提交的情况下,您将被重定向到 action 所在的页面。如果您希望无论如何都能更新,使用 [`applyAction`](#Progressive-enhancement-Customising-use:enhance) - 重置 `` 元素 - 在成功响应时使用 `invalidateAll` 使所有数据失效 - 在重定向响应时调用 `goto` - 如果发生错误,渲染最近的 `+error` 边界 - [将焦点重置](accessibility#Focus-management)到适当的元素 ### 自定义 use:enhance 要自定义行为,您可以提供一个 `SubmitFunction`,它会在表单提交前立即运行,并(可选地)返回一个随 `ActionResult` 一起运行的回调。请注意,如果您返回一个回调,上述默认行为将不会被触发。要恢复默认行为,请调用 `update`。 ```svelte { // `formElement` 是这个 `` 元素 // `formData` 是即将提交的 `FormData` 对象 // `action` 是表单提交的 URL // 调用 `cancel()` 将阻止提交 // `submitter` 是导致表单提交的 `HTMLElement` return async ({ result, update }) => { // `result` 是一个 `ActionResult` 对象 // `update` 是一个触发默认逻辑的函数,如果没有设置此回调 }; }} > ``` 您可以使用这些函数来显示和隐藏加载界面等。 如果您返回一个回调,您可能需要重现部分默认的 `use:enhance` 行为,但在成功响应时不使所有数据失效。您可以使用 `applyAction` 来实现: ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { // `result` 是一个 `ActionResult` 对象 +++ if (result.type === 'redirect') { goto(result.location); } else { await applyAction(result); }+++ }; }} > ``` `applyAction(result)` 的行为取决于 `result.type`: - `success`, `failure` — 将 `page.status` 设置为 `result.status`,并将 `form` 和 `page.form` 更新为 `result.data`(无论您从哪里提交,这与 `enhance` 的 `update` 形成对比) - `redirect` — 调用 `goto(result.location, { invalidateAll: true })` - `error` — 使用 `result.error` 渲染最近的 `+error` 边界 在所有情况下,[焦点将被重置](accessibility#Focus-management)。 ### 自定义事件监听器 我们也可以不使用 `use:enhance`,在 `` 上使用普通的事件监听器,自己实现渐进式增强: ```svelte
``` 请注意,在使用 `$app/forms` 中相应的方法进一步处理响应之前,需要 `deserialize` 响应。仅 `JSON.parse()` 是不够的,因为表单 action(如 `load` 函数)也支持返回 `Date` 或 `BigInt` 对象。 如果您在 `+page.server.js` 旁边有一个 `+server.js`,`fetch` 请求将默认路由到那里。要改为 `POST` 到 `+page.server.js` 中的 action ,请使用自定义的 `x-sveltekit-action` 头: ```js const response = await fetch(this.action, { method: 'POST', body: data, +++ headers: { 'x-sveltekit-action': 'true' }+++ }); ``` ## 替代方案 表单 action 是向服务端发送数据的首选方法,因为它们可以渐进式增强,但您也可以使用 [`+server.js`](routing#server) 文件来公开(例如)一个 JSON API。以下是这种交互的示例: ```svelte ``` ```js // @errors: 2355 1360 2322 /// file: src/routes/api/ci/+server.js /** @type {import('./$types').RequestHandler} */ export function POST() { // do something } ``` ## GET 与 POST 如我们所见,要调用表单 action ,必须使用 `method="POST"`。 有些表单不需要向服务端 `POST` 数据 —— 例如搜索输入。对于这些表单,您可以使用 `method="GET"`(或等效地,不指定 `method`),SvelteKit 将像处理 `
` 元素一样处理它们,使用客户端路由而不是完整页面导航: ```html
``` 提交此表单将导航到 `/search?q=...` 并调用您的 `load` 函数,但不会调用 action 。与 `
` 元素一样,您可以在 `
` 上设置 [`data-sveltekit-reload`](link-options#data-sveltekit-reload)、[`data-sveltekit-replacestate`](link-options#data-sveltekit-replacestate)、[`data-sveltekit-keepfocus`](link-options#data-sveltekit-keepfocus) 以及 [`data-sveltekit-noscroll`](link-options#data-sveltekit-noscroll) 属性,以控制路由器的行为。 ## 进一步阅读 - [教程:表单](/tutorial/kit/the-form-element) # 页面选项 默认情况下,SvelteKit 会首先在服务端上渲染(或[预渲染](glossary#Prerendering))组件,并将其以 HTML 的形式发送到客户端。然后会在浏览器中再次渲染该组件以使其具有交互性,这个过程称为[**hydration**](glossary#Hydration)。因此,您需要确保组件可以在这两个环境中都能运行。SvelteKit 随后会初始化一个[**路由器**](routing)来接管后续的导航。 您可以通过在 [`+page.js`](routing#page-page.js) 或 [`+page.server.js`](routing#page-page.server.js) 中导出选项来对每个页面分别进行控制,或者通过共享的 [`+layout.js`](routing#layout-layout.js) 或 [`+layout.server.js`](routing#layout-layout.server.js) 来控制一组页面。要为整个应用定义一个选项,可以从根布局中导出它。子布局和页面会覆盖父布局中设置的值,因此——例如——您可以为整个应用启用预渲染,然后仅对需要动态渲染的页面禁用它。 您可以在应用的不同区域混合使用这些选项。例如,您可以为营销页面进行预渲染以获得最大的速度,为动态页面进行服务端渲染以兼顾 SEO 和可访问性,并通过只在客户端渲染的方式将管理后台部分变成一个 SPA。这让 SvelteKit 非常灵活多变。 ## 预渲染 您的应用中很可能至少有一些路由可以表示为在构建时生成的简单 HTML 文件。这些路由可以被[_预渲染_](glossary#Prerendering)。 ```js /// file: +page.js/+page.server.js/+server.js export const prerender = true; ``` 或者,您也可以在根 `+layout.js` 或 `+layout.server.js` 中设置 `export const prerender = true` 来预渲染所有内容,并仅对显式标记为 _不可_ 预渲染的页面进行排除: ```js /// file: +page.js/+page.server.js/+server.js export const prerender = false; ``` 带有 `prerender = true` 的路由将从用于动态 SSR 的清单中排除,从而使您的服务端(或 serverless/edge functions)的体积更小。有些情况下您可能想要预渲染一个路由,但仍然希望将其包含在清单中(例如,对像 `/blog/[slug]` 这样的路由,您可能想预渲染最新/最受欢迎的内容,但对数量庞大的内容长尾部分使用服务端渲染)——对于这些情况,您可以使用第三种选项 `'auto'`: ```js /// file: +page.js/+page.server.js/+server.js export const prerender = 'auto'; ``` > [!NOTE] 如果整个应用都适合预渲染,您可以使用 [`adapter-static`](https://github.com/sveltejs/kit/tree/main/packages/adapter-static),它会输出适用于任何静态 Web 服务器的文件。 预渲染器会从应用的根目录开始,为它发现的所有可预渲染页面或 `+server.js` 路由生成文件。它会扫描每个页面中的指向其他可预渲染页面的 `` 元素——因此通常情况下,您不需要指定应该访问哪些页面。如果*确实*需要指定预渲染器应该访问哪些页面,可以通过 [`config.kit.prerender.entries`](configuration#prerender) 或在您的动态路由中导出一个 [`entries`](#entries) 函数来实现。 在预渲染时,从 [`$app/environment`]($app-environment) 中导入的 `building` 值将为 `true`。 ### 预渲染服务端路由 与其他页面选项不同,`prerender` 同样适用于 `+server.js` 文件。这些文件不受布局影响,但会继承从请求它们数据的页面(如果有的话)中设置的默认值。例如,如果 `+page.js` 包含以下 `load` 函数…… ```js /// file: +page.js export const prerender = true; /** @type {import('./$types').PageLoad} */ export async function load({ fetch }) { const res = await fetch('/my-server-route.json'); return await res.json(); } ``` ……那么如果 `src/routes/my-server-route.json/+server.js` 没有包含它自己的 `export const prerender = false`,它将被视为可预渲染。 ### 何时不进行预渲染 基本规则是:要使页面可预渲染,从服务端直接访问该页面时,无论是哪两个用户都应该得到相同的内容。 > [!NOTE] 并非所有页面都适合预渲染。任何被预渲染的内容都会被所有用户看到。当然,您可以在被预渲染的页面中通过 `onMount` 获取个性化数据,但这可能带来较差的用户体验,因为这会导致空白初始内容或加载指示器。 请注意,您仍然可以对基于页面参数加载数据的页面(如 `src/routes/blog/[slug]/+page.svelte`)进行预渲染。 在预渲染期间,禁止访问 [`url.searchParams`](load#Using-URL-data-url)。如果您需要使用它,请确保只在浏览器端使用(例如在 `onMount` 中)。 带有[actions](form-actions)的页面无法进行预渲染,因为必须要有一个服务端处理该 action 的 `POST` 请求。 ### 路由冲突 由于预渲染会写入文件系统,因此不可能同时生成目录和文件同名的两个端点。例如,`src/routes/foo/+server.js` 和 `src/routes/foo/bar/+server.js` 会尝试创建 `foo` 和 `foo/bar`,这是不可能的。 由于包含上述及其他原因,推荐始终使用文件扩展名——`src/routes/foo.json/+server.js` 和 `src/routes/foo/bar.json/+server.js` 将分别生成 `foo.json` 和 `foo/bar.json` 文件,并能和谐共存。 对于*页面*而言,我们通过写入 `foo/index.html` 而不是 `foo` 来规避此问题。 ### 故障排查 如果您遇到类似 “The following routes were marked as prerenderable, but were not prerendered” 的错误,则说明相关路由(或其父布局,如果它是某个页面)具有 `export const prerender = true`,但该页面未被预渲染爬虫访问,因此没有被预渲染。 由于这些路由无法进行动态服务端渲染,当用户尝试访问相关路由时会导致错误。可以通过以下几种方式修复: - 确保 SvelteKit 可以通过 [`config.kit.prerender.entries`](configuration#prerender) 或 [`entries`](#entries) 页面选项跟踪链接来找到这些路由。将动态路由(即带有 `[parameters]` 的页面)的链接添加到此选项中,如果它们无法通过爬取其他入口点找到,否则因为 SvelteKit 并不知道参数应取什么值,它们就不会被预渲染。未被标记为可预渲染的页面会被忽略,即使它们指向其他可预渲染页面,也不会被爬取。 - 确保 SvelteKit 可以在启用服务端渲染的其他预渲染页面中发现指向这些路由的链接。 - 将 `export const prerender = true` 更改为 `export const prerender = 'auto'`。带有 `'auto'` 的路由可以进行动态服务端渲染。 ## entries SvelteKit 会通过将 _入口点_ 作为起点并进行爬取,自动发现需要预渲染的页面。默认情况下,您的所有非动态路由都会被视作入口点——例如,如果您有如下路由…… ```bash / # 非动态 /blog # 非动态 /blog/[slug] # 动态,因为含有 `[slug]` ``` ……SvelteKit 会预渲染 `/` 和 `/blog`,并在此过程中发现类似 `` 这样的链接,从而对新页面进行预渲染。 在大多数情况下,这就足够了。在某些情况下,可能并不存在指向 `/blog/hello-world` 页面(或其并非存在于已预渲染页面中)的链接,此时我们需要告诉 SvelteKit 它们的存在。 可以通过 [`config.kit.prerender.entries`](configuration#prerender) 完成,也可以在属于动态路由的 `+page.js`、`+page.server.js` 或 `+server.js` 中导出一个 `entries` 函数: ```js /// file: src/routes/blog/[slug]/+page.server.js /** @type {import('./$types').EntryGenerator} */ export function entries() { return [{ slug: 'hello-world' }, { slug: 'another-blog-post' }]; } export const prerender = true; ``` `entries` 可以是一个 `async` 函数,这样您就如上例所示,从 CMS 或数据库检索文章列表。 ## ssr 通常,SvelteKit 会先在服务端上渲染您的页面,并将该 HTML 发送到客户端,在那里再进行[水合](glossary#Hydration)。如果您将 `ssr` 设置为 `false`,它会改为渲染一个空的“外壳”页面。这在您的页面无法在服务端上渲染时(例如使用了只在浏览器可用的全局对象 `document`)会有用,但在大多数情况下并不推荐这样做([请参阅附录](glossary#SSR))。 ```js /// file: +page.js export const ssr = false; // 如果 `ssr` 和 `csr` 都为 `false`,将不会渲染任何内容! ``` 如果您在根 `+layout.js` 中添加 `export const ssr = false`,那么整个应用只会在客户端被渲染——这实际上意味着您将应用变成了一个 SPA。 ## csr 通常,SvelteKit 会将服务端渲染的 HTML [水合](glossary#Hydration) 成交互式的客户端渲染 (CSR) 页面。有些页面根本不需要 JavaScript —— 很多博客文章或“关于”页面就是这种情况。对于这类页面,您可以禁用 CSR: ```js /// file: +page.js export const csr = false; // 如果 `csr` 和 `ssr` 都为 `false`,将不会渲染任何内容! ``` 禁用 CSR 不会向客户端发送任何 JavaScript。这意味着: - 网页只能通过 HTML 和 CSS 来工作。 - 所有 Svelte 组件中的 ` ``` ```svelte

Welcome {user().name}

``` > [!NOTE] 我们传递一个函数到 `setContext` 以保持跨边界的响应性。在[这里](/docs/svelte/$state#Passing-state-into-functions)阅读更多相关信息 > [!LEGACY] > 您也可以使用 `svelte/store` 中的 stores 来实现这一点,但在使用 Svelte 5 时,建议使用通用响应性。 在通过 SSR 渲染页面时,在更深层次的页面或组件中更新基于上下文的状态值不会影响父组件中的值,因为在状态值更新时父组件已经被渲染完成。 相比之下,在客户端(当启用 CSR 时,这是默认设置)这个值会被传播,层级更高的组件、页面和布局会对新值作出反应。因此,为了避免在水合过程中状态更新时值"闪烁",通常建议将状态向下传递给组件,而不是向上传递。 如果您不使用 SSR(并且可以保证将来也不需要使用 SSR),那么您可以安全地将状态保存在共享模块中,而无需使用 context API。 ## 组件和页面状态会被保留 当您在应用程序中导航时,SvelteKit 会复用现有的布局和页面组件。例如,如果您有这样的路由... ```svelte

{data.title}

Reading time: {Math.round(estimatedReadingTime)} minutes

{@html data.content}
``` ...那么从 `/blog/my-short-post` 导航到 `/blog/my-long-post` 不会导致布局、页面和其他组件被销毁和重新创建。相反,`data` 属性(以及 `data.title` 和 `data.content`)将会更新(就像任何其他 Svelte 组件一样),而且因为代码不会重新运行,像 `onMount` 和 `onDestroy` 这样的生命周期方法不会重新运行,`estimatedReadingTime` 也不会重新计算。 相反,我们需要使这个值变成[_响应式_](/tutorial/svelte/state): ```svelte /// file: src/routes/blog/[slug]/+page.svelte ``` > [!NOTE] 如果您需要在导航后重新运行 `onMount` 和 `onDestroy` 中的代码,您可以分别使用 [afterNavigate]($app-navigation#afterNavigate) 和 [beforeNavigate]($app-navigation#beforeNavigate)。 像这样复用组件意味着侧边栏滚动状态等会被保留,您可以轻松地在变化的值之间实现动画效果。如果您确实需要在导航时完全销毁并重新挂载一个组件,您可以使用这种模式: ```svelte {#key page.url.pathname} {/key} ``` ## 在 URL 中存储状态 如果您有需要让状态能够在页面重新加载后依然保持,比如表格上的过滤器或排序规则,URL 搜索参数(如 `?sort=price&order=ascending`)是存储它们的好地方。您可以把它们放在 `
` 或 `` 属性中,或通过 `goto('?key=value')` 以编程的方式设置它们。它们可以在 `load` 函数中通过 `url` 参数访问,在组件中通过 `page.url.searchParams` 访问。 ## 在快照中存储临时状态 某些 UI 状态,比如"列表是否展开?",是可以丢弃的 — 如果用户导航离开或刷新页面,状态丢失并不要紧。在某些情况下,您*确实*希望在用户导航到另一个页面并返回时数据能够保持,但将状态存储在 URL 或数据库中会显得过度。对于这种情况,SvelteKit 提供了 [快照](snapshots),让您可以将组件状态与历史记录条目关联起来。 # 构建您的应用 构建 SvelteKit 应用程序分为两个阶段,这两个阶段都发生在您运行 `vite build`(通常通过 `npm run build`)时。 首先,Vite 会为您的服务端代码、浏览器代码和 service worker(如果有的话)创建优化的生产构建。如果合适,[预渲染](page-options#prerender)会在此阶段执行。 其次,_适配器(adapter)_ 会对这个生产构建进行调整,使其适合目标环境 — 更多内容将在接下来的页面中介绍。 ## 构建过程中 SvelteKit 会在构建过程中加载您的 `+page/layout(.server).js` 文件(以及它们导入的所有文件)进行分析。 任何在此阶段不应该被执行的代码必须通过检查 [`$app/environment`]($app-environment) 的 `building` 是否为 `false`: ```js +++import { building } from '$app/environment';+++ import { setupMyDatabase } from '$lib/server/database'; +++if (!building) {+++ setupMyDatabase(); +++}+++ export function load() { // ... } ``` ## 预览您的应用 构建完成后,您可以通过 `vite preview`(通过 `npm run preview`)在本地查看您的生产构建。请注意,这将在 Node 中运行应用程序,因此无法完美复现您部署的应用程序 — 适配器特定的调整(如 [`platform` 对象](adapters#Platform-specific-context))不适用于预览。 # 适配器 在部署 SvelteKit 应用之前,您需要为您的部署目标进行*适配*。适配器是一些小型插件,它们接收构建好的应用作为输入,并生成用于部署的输出。 官方为多个平台提供了适配器 — 这些在以下页面中有详细文档: - [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) 用于 Cloudflare Pages - [`@sveltejs/adapter-cloudflare-workers`](adapter-cloudflare-workers) 用于 Cloudflare Workers - [`@sveltejs/adapter-netlify`](adapter-netlify) 用于 Netlify - [`@sveltejs/adapter-node`](adapter-node) 用于 Node 服务器 - [`@sveltejs/adapter-static`](adapter-static) 用于静态站点生成 (SSG) - [`@sveltejs/adapter-vercel`](adapter-vercel) 用于 Vercel 还有[社区提供的适配器](https://sveltesociety.dev/packages?category=sveltekit-adapters)用于其他平台。 ## 使用适配器 您的适配器在 `svelte.config.js` 中指定: ```js /// file: svelte.config.js // @filename: ambient.d.ts declare module 'svelte-adapter-foo' { const adapter: (opts: any) => import('@sveltejs/kit').Adapter; export default adapter; } // @filename: index.js // ---cut--- import adapter from 'svelte-adapter-foo'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter({ // 适配器选项在这里 }) } }; export default config; ``` ## 平台特定上下文 某些适配器可能可以访问关于请求的额外信息。例如,Cloudflare Workers 可以访问包含 KV 命名空间等的 `env` 对象。这可以作为 `platform` 属性传递给在[hooks](hooks)和[服务端路由](routing#server)中使用的 `RequestEvent` — 查看每个适配器的文档以了解更多信息。 # 零配置部署 当您使用 `npx sv create` 创建一个新的 SvelteKit 项目时,它默认会安装 [`adapter-auto`](https://github.com/sveltejs/kit/tree/main/packages/adapter-auto)。这个适配器会在您部署时自动安装并使用支持环境的适配器: - [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) 用于 [Cloudflare Pages](https://developers.cloudflare.com/pages/) - [`@sveltejs/adapter-netlify`](adapter-netlify) 用于 [Netlify](https://netlify.com/) - [`@sveltejs/adapter-vercel`](adapter-vercel) 用于 [Vercel](https://vercel.com/) - [`svelte-adapter-azure-swa`](https://github.com/geoffrich/svelte-adapter-azure-swa) 用于 [Azure Static Web Apps](https://docs.microsoft.com/en-us/azure/static-web-apps/) - [`svelte-kit-sst`](https://github.com/sst/sst/tree/master/packages/svelte-kit-sst) 用于 [通过 SST 部署到 AWS](https://sst.dev/docs/start/aws/svelte) - [`@sveltejs/adapter-node`](adapter-node) 用于 [Google Cloud Run](https://cloud.google.com/run) 建议您一旦确定了目标环境,就将相应的适配器安装到您的 `devDependencies` 中,因为这将把适配器添加到您的 lockfile 中,并略微改善 CI 上的安装时间。 ## 特定环境的配置 要添加配置选项,比如在 [`adapter-vercel`](adapter-vercel) 和 [`adapter-netlify`](adapter-netlify) 中的 `{ edge: true }`,您必须安装底层适配器 — `adapter-auto` 不接受任何选项。 ## 添加社区适配器 您可以通过编辑 [adapters.js](https://github.com/sveltejs/kit/blob/main/packages/adapter-auto/adapters.js) 并提交 PR 来添加对其他适配器的零配置支持。 # Node 服务端 要生成独立的 Node 服务端,请使用 [`adapter-node`](https://github.com/sveltejs/kit/tree/main/packages/adapter-node)。 ## 使用方法 使用 `npm i -D @sveltejs/adapter-node` 安装,然后将适配器添加到您的 `svelte.config.js`: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-node'; export default { kit: { adapter: adapter() } }; ``` ## 部署 首先,使用 `npm run build` 构建您的应用。这将在适配器选项中指定的输出目录(默认为 `build`)中创建生产服务端。 要运行应用程序,您需要输出目录、项目的 `package.json` 和 `node_modules` 中的生产依赖项。生产依赖项可以通过复制 `package.json` 和 `package-lock.json` 然后运行 `npm ci --omit dev` 来生成(如果您的应用没有任何依赖项,可以跳过此步骤)。然后您可以使用以下命令启动您的应用: ```bash node build ``` 开发依赖项将使用 [Rollup](https://rollupjs.org) 打包到您的应用中。要控制某个包是打包还是外部化,请将其分别放在 `package.json` 的 `devDependencies` 或 `dependencies` 中。 ### 压缩响应 通常您会希望压缩来自服务端的响应。如果您已经在为 SSL 或负载均衡部署了反向代理服务端,那么在该层处理压缩通常会带来更好的性能,因为 Node.js 是单线程的。 但是,如果您正在构建[自定义服务端](#Custom-server)并确实想在那里添加压缩中间件,请注意我们建议使用 [`@polka/compression`](https://www.npmjs.com/package/@polka/compression),因为 SvelteKit 会流式传输响应,而更流行的 `compression` 包不支持流式传输,使用时可能会导致错误。 ## 环境变量 在 `dev` 和 `preview` 模式下,SvelteKit 将从您的 `.env` 文件(或 `.env.local`,或 `.env.[mode]`,[由 Vite 决定](https://vitejs.dev/guide/env-and-mode.html#env-files))中读取环境变量。 在生产环境中,不会自动加载 `.env` 文件。要做到这一点,请在您的项目中安装 `dotenv`... ```bash npm install dotenv ``` ...并在运行构建的应用之前调用它: ```bash node +++-r dotenv/config+++ build ``` 如果您使用的是 Node.js v20.6+,您可以使用 [`--env-file`](https://nodejs.org/en/learn/command-line/how-to-read-environment-variables-from-nodejs) 标志代替: ```bash node +++--env-file=.env+++ build ``` ### `PORT`, `HOST` 和 `SOCKET_PATH` 默认情况下,服务端将使用 `0.0.0.0` 并在端口 3000 上接受连接。可以使用 `PORT` 和 `HOST` 环境变量对其进行自定义: ``` HOST=127.0.0.1 PORT=4000 node build ``` 或者,还可以配置服务端在指定的 socket 路径上接受连接。如果您使用 `SOCKET_PATH` 环境变量来执行此操作,则会忽略 `HOST` 和 `PORT` 环境变量。 ``` SOCKET_PATH=/tmp/socket node build ``` ### `ORIGIN`, `PROTOCOL_HEADER`, `HOST_HEADER` 和 `PORT_HEADER` HTTP 并不会为 SvelteKit 提供一种可靠的方法来获取当前请求的 URL。最简单的方式是设置 `ORIGIN` 环境变量来告诉 SvelteKit 应用在哪里被提供服务: ``` ORIGIN=https://my.site node build # 或者,例如本地预览和测试 ORIGIN=http://localhost:3000 node build ``` 这样,当请求 `/stuff` 路径名时,就能正确解析到 `https://my.site/stuff`。或者,您可以指定用于告诉 SvelteKit 关于请求协议和主机的标头,由此 SvelteKit 可以构建 origin URL: ``` PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build ``` > [!NOTE] [`x-forwarded-proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto) 和 [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host) 是事实上的标准请求头,用于在使用反向代理(如负载均衡器和 CDN)时转发原始协议和主机。只有在您的服务端位于受信任的反向代理之后时,才应设置这些变量;否则,客户端可能会伪造这些标头。 > 如果您在非标准端口托管代理,并且您的反向代理支持 `x-forwarded-port`,您也可以设置 `PORT_HEADER=x-forwarded-port`。 如果 `adapter-node` 无法正确确定您的部署的 URL,在使用 [表单 actions](form-actions) 时可能会出现以下错误: > [!NOTE] Cross-site POST form submissions are forbidden ### `ADDRESS_HEADER` 和 `XFF_DEPTH` 传递给 hooks 和端点的 [RequestEvent](@sveltejs-kit#RequestEvent) 对象包含一个 `event.getClientAddress()` 函数,用于返回客户端的 IP 地址。默认情况下,这是发起连接的 `remoteAddress`。如果您的服务器位于一个或多个代理(如负载均衡器)之后,这个值将包含最内部代理的 IP 地址,而不是客户端的 IP 地址,因此我们需要指定一个 `ADDRESS_HEADER` 读取地址: ``` ADDRESS_HEADER=True-Client-IP node build ``` > [!NOTE] 标头很容易被伪造。与 `PROTOCOL_HEADER` 和 `HOST_HEADER` 一样,只有在您[了解相关风险](https://adam-p.ca/blog/2022/03/x-forwarded-for/)的情况下才应设置这些变量。 如果 `ADDRESS_HEADER` 是 `X-Forwarded-For`,其值会包含用逗号分隔的 IP 地址列表。此时应通过 `XFF_DEPTH` 环境变量指定在您的服务器前有多少个受信任的代理。例如,如果有三个受信任的代理,代理 3 会转发客户端原始连接和前两个代理的地址: ``` , , ``` 有些指南会告诉您读取最左边的地址,但这样会[容易被伪造](https://adam-p.ca/blog/2022/03/x-forwarded-for/): ``` , , , ``` 因此,我们从右侧读取,并依据受信任的代理数量进行处理。在这个示例里,我们会使用 `XFF_DEPTH=3`。 > [!NOTE] 如果您确实需要读取最左侧的地址(并且不在意被伪造)——例如提供地理位置服务,在此情况下,IP 地址“真实性”比“可信度”更重要,您可以在应用中自行检查 `x-forwarded-for` 标头来实现这一点。 ### `BODY_SIZE_LIMIT` 接受的最大请求体大小,以字节为单位(包括流式传输时)。 请求体大小也可以使用单位后缀指定,包括千字节(K)、兆字节(M)或千兆字节(G)。例如 `512K` 或 `1M`。默认值为 512kb。 您可以设置值为 `Infinity`(在适配器的旧版本中为 0)来禁用此选项,如果需要更高级的功能,可以在 [`handle`](hooks#Server-hooks-handle) 中自行实现更高级的检查逻辑。 ### `SHUTDOWN_TIMEOUT` 接收到 `SIGTERM` 或 `SIGINT` 信号后,在强制关闭任何剩余连接之前等待的秒数。默认值是 `30`。在内部,适配器会调用 [`closeAllConnections`](https://nodejs.org/api/http.html#servercloseallconnections)。更多细节请参见 [优雅关闭](#Graceful-shutdown)。 ### `IDLE_TIMEOUT` 在使用 systemd 套接字激活时,`IDLE_TIMEOUT` 用于指定当应用在没有请求的情况下经过多少秒会自动休眠。如果未设置,则应用会一直运行。详见 [套接字激活](#Socket-activation) 获取更多信息。 ## Options 该适配器可以通过多种选项进行配置: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-node'; export default { kit: { adapter: adapter({ // 以下为默认选项 out: 'build', precompress: true, envPrefix: '' }) } }; ``` ### out 构建服务端输出的目录,默认为 `build` —— 也就是说,如果您使用默认目录,执行 `node build` 将在本地启动服务端。 ### precompress 使用 gzip 和 brotli 对资源和预渲染页面进行预压缩。默认值为 `true`。 ### envPrefix 如果您需要更改用于配置部署的环境变量名称(例如,与您无法控制的环境变量冲突),可以指定一个前缀: ```js envPrefix: 'MY_CUSTOM_'; ``` ```sh MY_CUSTOM_HOST=127.0.0.1 \ MY_CUSTOM_PORT=4000 \ MY_CUSTOM_ORIGIN=https://my.site \ node build ``` ## 优雅关闭 默认情况下,当接收到 `SIGTERM` 或 `SIGINT` 信号时,`adapter-node` 会优雅地关闭 HTTP 服务器。它将: 1. 拒绝新的请求([`server.close`](https://nodejs.org/api/http.html#serverclosecallback)) 2. 等待已经发出的请求但尚未收到响应的请求完成,并在连接变为空闲后关闭连接([`server.closeIdleConnections`](https://nodejs.org/api/http.html#servercloseidleconnections)) 3. 最后,在超过 [`SHUTDOWN_TIMEOUT`](#Environment-variables-SHUTDOWN_TIMEOUT) 秒后强制关闭所有仍处于活动状态的连接([`server.closeAllConnections`](https://nodejs.org/api/http.html#servercloseallconnections))。 > [!NOTE] 如果您想自定义这一行为,您可以使用[自定义服务端](#Custom-server)。 您还可以监听 `sveltekit:shutdown` 事件,该事件会在 HTTP 服务器关闭全部连接后触发。与 Node 的 `exit` 事件不同,`sveltekit:shutdown` 事件支持异步操作,并且无论服务器是否有未完成的任务(如未关闭的数据库连接),在所有连接都关闭后都会被触发: ```js // @errors: 2304 process.on('sveltekit:shutdown', async (reason) => { await jobs.stop(); await db.close(); }); ``` 参数 `reason` 的可能取值包括: - `SIGINT` - 关机由 `SIGINT` 信号触发 - `SIGTERM` - 关机由 `SIGTERM` 信号触发 - `IDLE` - 关机由 [`IDLE_TIMEOUT`](#Environment-variables-IDLE_TIMEOUT) 触发 ## 套接字激活 当今大多数 Linux 操作系统都使用名为 systemd 的现代进程管理器来启动、运行和管理服务。您可以配置服务器来分配一个套接字,并在需要时按需启动应用。这被称为 [套接字激活](http://0pointer.de/blog/projects/socket-activated-containers.html)。在这种情况下,操作系统会向您的应用传递两个环境变量:`LISTEN_PID` 和 `LISTEN_FDS`。然后,适配器会在文件描述符 3 上进行监听,该描述符对应您创建的 systemd 套接字单元。 > [!NOTE] 您仍然可以在 systemd 套接字激活中使用 [`envPrefix`](#Options-envPrefix)。`LISTEN_PID` 和 `LISTEN_FDS` 始终无需前缀即可读取。 要利用套接字激活,请按以下步骤操作: 1. 让您的应用作为一个 [systemd 服务](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html) 运行。它既可以直接运行在主机系统上,也可以在容器内(例如使用 Docker 或 systemd 可移植服务)运行。 如果您额外向应用传递一个 [`IDLE_TIMEOUT`](#Environment-variables-IDLE_TIMEOUT) 环境变量,它将在没有请求持续 `IDLE_TIMEOUT` 秒时,优雅地关闭。之后如果有新的请求到来,systemd 将自动重新启动您的应用。 ```ini /// file: /etc/systemd/system/myapp.service [Service] Environment=NODE_ENV=production IDLE_TIMEOUT=60 ExecStart=/usr/bin/node /usr/bin/myapp/build ``` 2. 创建一个配套的 [socket 单元](https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html)。适配器仅接受单个 socket。 ```ini /// file: /etc/systemd/system/myapp.socket [Socket] ListenStream=3000 [Install] WantedBy=sockets.target ``` 3. 通过运行 `sudo systemctl daemon-reload` 确保 systemd 识别了这两个单元。然后使用 `sudo systemctl enable --now myapp.socket` 在启动时启用该 socket 并立即启动它。这样当第一个请求到达 `localhost:3000` 时,应用将自动启动。 ## 自定义服务端 该适配器会在您的构建目录中创建两个文件——`index.js` 和 `handler.js`。运行 `index.js`(例如,如果您使用默认 `build` 目录,那么执行 `node build`)将会在指定端口上启动服务器。 或者,您可以导入 `handler.js` 文件,它导出一个兼容 [Express](https://github.com/expressjs/express)、[Connect](https://github.com/senchalabs/connect) 或 [Polka](https://github.com/lukeed/polka) (甚至是内置的 [`http.createServer`](https://nodejs.org/dist/latest/docs/api/http.html#httpcreateserveroptions-requestlistener))的处理程序,并且设置你自己的服务器: ```js // @errors: 2307 7006 /// file: my-server.js import { handler } from './build/handler.js'; import express from 'express'; const app = express(); // 添加一个独立于 SvelteKit 应用的路由 app.get('/healthcheck', (req, res) => { res.end('ok'); }); // 让 SvelteKit 处理其他所有内容,包括提供预渲染页面和静态资源 app.use(handler); app.listen(3000, () => { console.log('listening on port 3000'); }); ``` # 静态站点生成 要将 SvelteKit 用作静态站点生成器(SSG),请使用 [`adapter-static`](https://github.com/sveltejs/kit/tree/main/packages/adapter-static)。 这将把您的整个站点预渲染为静态文件集合。如果您想只预渲染某些页面而动态渲染其他页面,您需要使用不同的适配器,并结合[`prerender` 选项](page-options#prerender)。 ## 使用方法 通过 `npm i -D @sveltejs/adapter-static` 安装,然后将适配器添加到您的 `svelte.config.js` 中: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; export default { kit: { adapter: adapter({ // 显示默认选项。在某些平台上 // 这些选项会自动设置 — 见下文 pages: 'build', assets: 'build', fallback: undefined, precompress: false, strict: true }) } }; ``` ...并在根布局中添加 [`prerender`](page-options#prerender) 选项: ```js /// file: src/routes/+layout.js // 如果您使用 fallback(即 SPA 模式),这可以设为 false export const prerender = true; ``` > [!NOTE] 您必须确保 SvelteKit 的 [`trailingSlash`](page-options#trailingSlash) 选项适合您的环境。如果您的主机在收到 `/a` 的请求时不渲染 `/a.html`,那么您需要在根布局中设置 `trailingSlash: 'always'` 以创建 `/a/index.html`。 ## 零配置支持 部分平台已支持零配置(未来将支持更多): - [Vercel](https://vercel.com) 在这些平台上,您应该省略适配器选项,以便 `adapter-static` 可以提供最佳配置: ```js // @errors: 2304 /// file: svelte.config.js export default { kit: { adapter: adapter(---{...}---) } }; ``` ## 选项 ### pages 预渲染页面的写入目录。默认为 `build`。 ### assets 写入静态资源(包括 `static` 的内容,以及 SvelteKit 生成的客户端 JS 和 CSS)的目录。通常这应该与 `pages` 相同,它会默认使用 `pages` 的值,但在极少数情况下,您可能需要将页面和资源输出到不同的位置。 ### fallback 为 [SPA 模式](single-page-apps) 指定一个后备页面,例如 `index.html` 或 `200.html` 或 `404.html`。 ### precompress 如果为 `true`,则使用 brotli 和 gzip 预压缩文件。这将生成 `.br` 和 `.gz` 文件。 ### strict 默认情况下,`adapter-static` 会检查您的应用的所有页面和端点(如果有)是否都已预渲染,或者您是否设置了 `fallback` 选项。这个检查的存在是为了防止您意外发布一个部分内容无法访问的应用,因为这些内容不包含在最终输出中。如果您知道这是可以的(例如当某个页面只在特定条件下存在时),您可以将 `strict` 设置为 `false` 来关闭这个检查。 ## GitHub Pages 在为 [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages) 构建时,如果您的仓库名称不等同于 `your-username.github.io`,请确保更新 [`config.kit.paths.base`](configuration#paths) 以匹配您的仓库名称。这是因为站点将从 `https://your-username.github.io/your-repo-name` 而不是根目录提供服务。 您还需要生成一个后备 `404.html` 页面来替换 GitHub Pages 显示的默认 404 页面。 GitHub Pages 的配置可能如下所示: ```js // @errors: 2307 2322 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; /** @type {import('@sveltejs/kit').Config} */ const config = { kit: { adapter: adapter({ fallback: '404.html' }), paths: { base: process.argv.includes('dev') ? '' : process.env.BASE_PATH } } }; export default config; ``` 您可以使用 GitHub actions 在进行更改时自动将您的站点部署到 GitHub Pages。以下是一个工作流示例: ```yaml ### file: .github/workflows/deploy.yml name: Deploy to GitHub Pages on: push: branches: 'main' jobs: build_site: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 # 如果您使用 pnpm,添加此步骤然后更改下面的命令和缓存键以使用 `pnpm` # - name: Install pnpm # uses: pnpm/action-setup@v3 # with: # version: 8 - name: Install Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: npm - name: Install dependencies run: npm install - name: build env: BASE_PATH: '/${{ github.event.repository.name }}' run: | npm run build - name: Upload Artifacts uses: actions/upload-pages-artifact@v3 with: # 这应该与适配器静态选项中的 `pages` 选项匹配 path: 'build/' deploy: needs: build_site runs-on: ubuntu-latest permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - name: Deploy id: deployment uses: actions/deploy-pages@v4 ``` 如果您不使用 GitHub actions 部署您的站点(例如,您将构建的站点推送到它自己的仓库),请在您的 `static` 目录中添加一个空的 `.nojekyll` 文件,以防止 Jekyll 干扰。 # 单页应用 通过在根布局中禁用 SSR,您可以将使用任何适配器的任何 SvelteKit 应用转换为完全客户端渲染的单页应用(SPA): ```js /// file: src/routes/+layout.js export const ssr = false; ``` > [!NOTE] 在大多数情况下不推荐这样做:它会损害 SEO,往往会降低感知性能,并且如果 JavaScript 失败或被禁用(这种情况发生的[频率可能比您想象的要高](https://kryogenix.org/code/browser/everyonehasjs.html)),会使您的应用对用户不可访问。 如果您没有任何服务端逻辑(即没有 `+page.server.js`、`+layout.server.js` 或 `+server.js` 文件),您可以使用 [`adapter-static`](adapter-static) 通过添加一个*后备页面*来创建您的 SPA。 ## 用法 使用 `npm i -D @sveltejs/adapter-static` 安装,然后在您的 `svelte.config.js` 中添加适配器,并使用以下选项: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-static'; export default { kit: { adapter: adapter({ fallback: '200.html' // 可能因主机而异 }) } }; ``` `fallback` 页面是 SvelteKit 根据您的页面模板(例如 `app.html`)创建的 HTML 页面,用于加载您的应用并导航到正确的路由。例如静态网站托管服务 [Surge](https://surge.sh/help/adding-a-200-page-for-client-side-routing),允许您添加一个 `200.html` 文件,用于处理任何不对应于静态资源或预渲染页面的请求。 在某些主机上,它可能是 `index.html` 或完全不同的名称 — 请查阅您的平台文档。 > [!NOTE] 请注意,无论 [`paths.relative`](configuration#paths) 的值如何,后备页面将始终包含绝对资源路径(即以 `/` 而不是 `.` 开头),因为它用于响应任意路径的请求。 ## Apache 要在 [Apache](https://httpd.apache.org/) 上运行 SPA,您应该添加一个 `static/.htaccess` 文件,将请求路由到后备页面: ``` RewriteEngine On RewriteBase / RewriteRule ^200\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /200.html [L] ``` ## 预渲染单个页面 如果您想预渲染特定页面,您可以仅对应用的这些部分重新启用 `ssr` 和 `prerender`: ```js /// file: src/routes/my-prerendered-page/+page.js export const prerender = true; export const ssr = true; ``` # Cloudflare Pages 要部署到 [Cloudflare Pages](https://developers.cloudflare.com/pages/),请使用 [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare)。 当您使用 [`adapter-auto`](adapter-auto) 时,此适配器将被默认安装。如果您计划继续使用 Cloudflare Pages,您可以从 [`adapter-auto`](adapter-auto) 切换到直接使用这个适配器,这样在本地开发时可以模拟 Cloudflare Workers 特定的值,类型声明会自动应用,并且提供设置 Cloudflare 特定选项的功能。 ## 比较 - `adapter-cloudflare` – 支持所有 SvelteKit 功能;为 [Cloudflare Pages](https://blog.cloudflare.com/cloudflare-pages-goes-full-stack/) 构建 - `adapter-cloudflare-workers` – 支持所有 SvelteKit 功能;为 Cloudflare Workers 构建 - `adapter-static` – 仅生成客户端静态资源;兼容 Cloudflare Pages ## 使用方法 使用 `npm i -D @sveltejs/adapter-cloudflare` 安装,然后将适配器添加到您的 `svelte.config.js` 中: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-cloudflare'; export default { kit: { adapter: adapter({ // 以下选项的说明请见下文 routes: { include: ['/*'], exclude: [''] }, platformProxy: { configPath: 'wrangler.toml', environment: undefined, experimentalJsonConfig: false, persist: false } }) } }; ``` ## 选项 ### routes 允许您自定义由 `adapter-cloudflare` 生成的 [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) 文件。 - `include` 定义将调用函数的路由,默认为 `['/*']` - `exclude` 定义将*不*调用函数的路由 — 这是一种更快且更经济的方式来提供应用的静态资源。这个数组可以包含以下特殊值: - `` 包含您的应用构建产物(由 Vite 生成的文件) - `` 包含您的 `static` 目录的内容 - `` 包含预渲染页面的列表 - `` (默认值) 包含以上所有内容 您可以组合使用最多 100 个 `include` 和 `exclude` 规则。通常您可以省略 `routes` 选项,但如果(例如)您的 `` 路径超过了该限制,您可能会发现手动创建一个包含 `'/articles/*'` 的 `exclude` 列表比自动生成的 `['/articles/foo', '/articles/bar', '/articles/baz', ...]` 更有帮助。 ### platformProxy 模拟 `platform.env` 本地绑定的偏好设置。完整的选项列表请参见 [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#syntax) Wrangler API 文档。 ## 部署 请按照 Cloudflare Pages 的[入门指南](https://developers.cloudflare.com/pages/get-started)开始。 配置项目设置时,您必须使用以下设置: - **框架预设** – SvelteKit - **构建命令** – `npm run build` 或 `vite build` - **构建输出目录** – `.svelte-kit/cloudflare` ## 运行时 API [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) 对象包含您项目的[绑定](https://developers.cloudflare.com/pages/platform/functions/bindings/),包括 KV/DO 命名空间等。它通过 `platform` 属性传递给 SvelteKit,同时还有 [`context`](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil)、[`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/) 和 [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties),这意味着您可以在 hooks 和端点中访问它: ```js // @errors: 7031 export async function POST({ request, platform }) { const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` > [!NOTE] 应优先使用 SvelteKit 内置的 `$env` 模块来处理环境变量。 要为您的绑定包含类型声明,请在您的 `src/app.d.ts` 中引用它们: ```ts /// file: src/app.d.ts import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types'; declare global { namespace App { interface Platform { +++ env?: { YOUR_KV_NAMESPACE: KVNamespace; YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; };+++ } } } export {}; ``` ### 本地测试 在开发和预览模式下会模拟 `platform` 属性中的 Cloudflare Workers 特定值。本地[绑定](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings)是基于您的 `wrangler.toml` 文件中的配置创建的,并在开发和预览期间用于填充 `platform.env`。使用适配器配置中的 [`platformProxy` 选项](#Options-platformProxy) 来更改您的绑定偏好设置。 要测试构建,您应该使用 [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) **版本 3**。构建完网站后,运行 `wrangler pages dev .svelte-kit/cloudflare`。 ## 注意事项 项目根目录中的 `/functions` 目录中的函数将*不会*包含在部署中,这些函数会被编译到一个[单一的 `_worker.js` 文件](https://developers.cloudflare.com/pages/platform/functions/#advanced-mode)中。函数应该作为 [服务端点](routing#server) 在您的 SvelteKit 应用中实现。 特定于 Cloudflare Pages 的 `_headers` 和 `_redirects` 文件可以通过将它们放入 `/static` 文件夹来用于静态资源响应(如图片)。 然而,它们对 SvelteKit 动态渲染的响应没有影响,这些响应应该从 [服务端点](routing#server) 或通过 [`handle`](hooks#Server-hooks-handle) 钩子返回自定义头部或重定向响应。 ## 故障排除 ### 进一步阅读 您可能想参考 [Cloudflare 关于部署 SvelteKit 站点的文档](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-site)。 ### 访问文件系统 您不能在 Cloudflare Workers 中使用 `fs` — 您必须[预渲染](page-options#prerender)相关路由。 # Cloudflare Workers 要部署到 [Cloudflare Workers](https://workers.cloudflare.com/),请使用 [`adapter-cloudflare-workers`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare-workers)。 > [!NOTE] 除非您有特别原因使用 `adapter-cloudflare-workers`,否则建议使用 `adapter-cloudflare`。两个适配器具有相同的功能,但 Cloudflare Pages 提供了更多功能,如 GitHub 集成自动构建和部署、预览部署、即时回滚等。 ## 使用方法 使用 `npm i -D @sveltejs/adapter-cloudflare-workers` 安装,然后将适配器添加到您的 `svelte.config.js` 中: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-cloudflare-workers'; export default { kit: { adapter: adapter({ config: 'wrangler.toml', platformProxy: { configPath: 'wrangler.toml', environment: undefined, experimentalJsonConfig: false, persist: false } }) } }; ``` ## 选项 ### config 自定义 `wrangler.toml` 或 `wrangler.json` 配置文件的路径。 ### platformProxy 模拟的 `platform.env` 本地绑定的首选项。完整的选项列表请参见 [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#syntax) Wrangler API 文档。 ## 基本配置 此适配器需要在项目根目录中找到 [wrangler.toml/wrangler.json](https://developers.cloudflare.com/workers/platform/sites/configuration) 文件。它应该看起来像这样: ```toml /// file: wrangler.toml name = "" account_id = "" main = "./.cloudflare/worker.js" site.bucket = "./.cloudflare/public" build.command = "npm run build" compatibility_date = "2021-11-12" workers_dev = true ``` `` 可以是任何名称。`` 可以通过登录到您的 [Cloudflare 仪表板](https://dash.cloudflare.com) 并从 URL 末尾获取: ``` https://dash.cloudflare.com/ ``` > [!NOTE] 您应该将 `.cloudflare` 目录(或您为 `main` 和 `site.bucket` 指定的任何目录)添加到 `.gitignore` 中。 如果您还没有安装 [wrangler](https://developers.cloudflare.com/workers/wrangler/get-started/) 并登录,需要先执行这些操作: ``` npm i -g wrangler wrangler login ``` 然后,您可以构建并部署您的应用: ```sh wrangler deploy ``` ## 自定义配置 如果您想使用 `wrangler.toml` 以外的配置文件,可以使用 [`config` 选项](#Options-config) 指定。 如果您想启用 [Node.js 兼容性](https://developers.cloudflare.com/workers/runtime-apis/nodejs/#enable-nodejs-from-the-cloudflare-dashboard),可以在 `wrangler.toml` 中添加 "nodejs_compat" 标志: ```toml /// file: wrangler.toml compatibility_flags = [ "nodejs_compat" ] ``` ## 运行时 API [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) 对象包含您项目的 [绑定](https://developers.cloudflare.com/pages/platform/functions/bindings/),包括 KV/DO 命名空间等。它通过 `platform` 属性传递给 SvelteKit,同时还包括 [`context`](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil)、[`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/) 和 [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties),这意味着您可以在 hooks 和端点中访问它: ```js // @errors: 7031 export async function POST({ request, platform }) { const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` > [!NOTE] 对于环境变量,应优先使用 SvelteKit 内置的 `$env` 模块。 要包含绑定的类型声明,请在您的 `src/app.d.ts` 中引用它们: ```ts /// file: src/app.d.ts import { KVNamespace, DurableObjectNamespace } from '@cloudflare/workers-types'; declare global { namespace App { interface Platform { +++ env?: { YOUR_KV_NAMESPACE: KVNamespace; YOUR_DURABLE_OBJECT_NAMESPACE: DurableObjectNamespace; };+++ } } } export {}; ``` ### 本地测试 在开发和预览模式下,`platform` 属性中的 Cloudflare Workers 特定值会被模拟。本地 [绑定](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) 是基于您的 `wrangler.toml` 文件中的配置创建的,并在开发和预览期间用于填充 `platform.env`。使用适配器配置的 [`platformProxy` 选项](#Options-platformProxy) 可以更改绑定的首选项。 要测试构建,您应该使用 [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) **版本 3**。构建完网站后,运行 `wrangler dev`。 ## 故障排除 ### Worker 大小限制 在部署到 workers 时,SvelteKit 生成的服务端会被打包成单个文件。如果您的 worker 在压缩后超过了 [大小限制](https://developers.cloudflare.com/workers/platform/limits/#worker-size),Wrangler 将无法发布。通常您不太可能遇到这个限制,但某些大型库可能会导致这种情况。在这种情况下,您可以尝试通过仅在客户端导入这些库来减小 worker 的大小。更多信息请参见 [FAQ](./faq#How-do-I-use-X-with-SvelteKit-How-do-I-use-a-client-side-only-library-that-depends-on-document-or-window)。 ### 访问文件系统 您不能在 Cloudflare Workers 中使用 `fs` — 您必须 [预渲染](page-options#prerender) 相关路由。 # Netlify 要部署到 Netlify,请使用 [`adapter-netlify`](https://github.com/sveltejs/kit/tree/main/packages/adapter-netlify)。 当您使用 [`adapter-auto`](adapter-auto) 时,此适配器将默认安装,但将其添加到您的项目中可以指定 Netlify 特定的选项。 ## 用法 使用 `npm i -D @sveltejs/adapter-netlify` 安装,然后将适配器添加到您的 `svelte.config.js`: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-netlify'; export default { kit: { // 显示默认选项 adapter: adapter({ // 如果为 true,将创建 Netlify Edge Function 而不是 // 使用标准的基于 Node 的函数 edge: false, // 如果为 true,将把您的应用拆分为多个函数 // 而不是为整个应用创建单个函数。 // 如果 `edge` 为 true,则不能使用此选项 split: false }) } }; ``` 然后,确保在项目根目录中有一个 [netlify.toml](https://docs.netlify.com/configure-builds/file-based-configuration) 文件。这将根据 `build.publish` 设置确定写入静态资源的位置,如此示例配置所示: ```toml [build] command = "npm run build" publish = "build" ``` 如果缺少 `netlify.toml` 文件或 `build.publish` 值,将使用默认值 `"build"`。请注意,如果您在 Netlify UI 中将发布目录设置为其他值,那么您也需要在 `netlify.toml` 中设置它,或使用默认值 `"build"`。 ### Node 版本 新项目默认将使用当前的 Node LTS 版本。但是,如果您正在升级很久以前创建的项目,它可能会停留在旧版本上。有关手动指定当前 Node 版本的详细信息,请参阅 [Netlify 文档](https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript)。 ## Netlify Edge Functions SvelteKit 支持 [Netlify Edge Functions](https://docs.netlify.com/netlify-labs/experimental-features/edge-functions/)。如果您向 `adapter` 函数传递 `edge: true` 选项,服务端渲染将在部署在靠近站点访问者的基于 Deno 的边缘函数中进行。如果设置为 `false`(默认值),站点将部署到基于 Node 的 Netlify Functions。 ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-netlify'; export default { kit: { adapter: adapter({ // 将使用基于 Deno 的 Netlify Edge Function // 而不是使用标准的基于 Node 的函数 edge: true }) } }; ``` ## SvelteKit 功能的 Netlify 替代方案 您可以直接使用 SvelteKit 提供的功能构建应用,而无需依赖任何 Netlify 功能。使用这些功能的 SvelteKit 版本将允许它们在开发模式下使用,通过集成测试进行测试,并在您决定切换到其他适配器时能够正常工作。但是,在某些情况下,使用这些功能的 Netlify 版本可能会更有利。例如,如果您正在将已经托管在 Netlify 上的应用迁移到 SvelteKit。 ### 重定向规则 在编译期间,重定向规则会自动附加到您的 `_redirects` 文件中。(如果该文件尚不存在,它将被创建。)这意味着: - `netlify.toml` 中的 `[[redirects]]` 永远不会匹配,因为 `_redirects` 具有[更高的优先级](https://docs.netlify.com/routing/redirects/#rule-processing-order)。因此,始终将您的规则放在 [`_redirects` 文件](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file)中。 - `_redirects` 不应该有任何自定义的"捕获所有"规则,如 `/* /foobar/:splat`。否则,由于 Netlify 只处理[第一个匹配的规则](https://docs.netlify.com/routing/redirects/#rule-processing-order),自动附加的规则将永远不会被应用。 ### Netlify Forms 1. 按照[这里](https://docs.netlify.com/forms/setup/#html-forms)的描述创建您的 Netlify HTML 表单,例如作为 `/routes/contact/+page.svelte`。(别忘了添加隐藏的 `form-name` input 元素!) 2. Netlify 的构建机器人在部署时解析您的 HTML 文件,这意味着您的表单必须[预渲染](page-options#prerender)为 HTML 。您可以在您的 `contact.svelte` 中添加 `export const prerender = true` 来仅预渲染该页面,或设置 `kit.prerender.force: true` 选项来预渲染所有页面。 3. 如果您的 Netlify 表单有一个[自定义成功消息](https://docs.netlify.com/forms/setup/#success-messages),如 ``,则确保相应的 `/routes/success/+page.svelte` 存在并已预渲染。 ### Netlify Functions 使用此适配器,SvelteKit 端点将作为 [Netlify Functions](https://docs.netlify.com/functions/overview/) 托管。Netlify 函数处理程序具有额外的上下文,包括 [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) 信息。您可以通过您的 hooks 和 `+page.server` 或 `+layout.server` 端点中的 `event.platform.context` 字段访问此上下文。当适配器配置中的 `edge` 属性为 `false` 时,这些是[serverless functions](https://docs.netlify.com/functions/overview/),当为 `true` 时,这些是[edge functions](https://docs.netlify.com/edge-functions/overview/#app)。 ```js // @errors: 2705 7006 /// file: +page.server.js export const load = async (event) => { const context = event.platform.context; console.log(context); // 在 Netlify 应用的函数日志中显示 }; ``` 此外,您可以通过创建一个目录并在 `netlify.toml` 文件中添加配置来添加您自己的 Netlify 函数。例如: ```toml [build] command = "npm run build" publish = "build" [functions] directory = "functions" ``` ## 故障排除 ### 访问文件系统 您不能在 edge 部署中使用 `fs`。 您*可以*在 serverless 部署中使用它,但它不会按预期工作,因为文件不会从您的项目复制到部署中。相反,使用 `$app/server` 中的 [`read`]($app-server#read) 函数来访问您的文件。`read` 在 edge 部署中不起作用(这在将来可能会改变)。 或者,您可以[预渲染](page-options#prerender)相关路由。 # Vercel 要部署到 Vercel,请使用 [`adapter-vercel`](https://github.com/sveltejs/kit/tree/main/packages/adapter-vercel)。 当您使用 [`adapter-auto`](adapter-auto) 时,这个适配器会被默认安装,但将其添加到您的项目中可以让您指定 Vercel 特定的选项。 ## 用法 使用 `npm i -D @sveltejs/adapter-vercel` 安装,然后将适配器添加到您的 `svelte.config.js` 中: ```js // @errors: 2307 2345 /// file: svelte.config.js import adapter from '@sveltejs/adapter-vercel'; export default { kit: { adapter: adapter({ // 可以在此处设置选项,详见下文 }) } }; ``` ## 部署配置 要控制您的路由如何作为函数部署到 Vercel,您可以通过上面显示的选项或在 `+server.js`、`+page(.server).js` 和 `+layout(.server).js` 文件中使用 [`export const config`](page-options#config) 来指定部署配置。 例如,您可以将应用的某些部分部署为 [Edge Functions](https://vercel.com/docs/concepts/functions/edge-functions)... ```js /// file: about/+page.js /** @type {import('@sveltejs/adapter-vercel').Config} */ export const config = { runtime: 'edge' }; ``` ...其他部分则作为 [Serverless Functions](https://vercel.com/docs/concepts/functions/serverless-functions)(注意,在布局中指定 `config` 时,它会应用于所有子页面): ```js /// file: admin/+layout.js /** @type {import('@sveltejs/adapter-vercel').Config} */ export const config = { runtime: 'nodejs22.x' }; ``` 以下选项适用于所有函数: - `runtime`:`'edge'`、`'nodejs18.x'`、`'nodejs20.x'` 或 `'nodejs22.x'`。默认情况下,适配器会选择与您的项目在 Vercel 仪表板上配置的 Node 版本对应的 `'nodejs.x'` - `regions`:[边缘网络区域](https://vercel.com/docs/concepts/edge-network/regions)数组(对于 serverless 函数默认为 `["iad1"]`)或当 `runtime` 为 `edge` 时为 `'all'`(其默认值)。注意,serverless 函数的多区域部署仅在企业版计划中支持 - `split`:如果为 `true`,会导致路由被部署为独立函数。如果在适配器级别将 `split` 设置为 `true`,所有路由都将被部署为独立函数 此外,以下选项适用于 edge functions: - `external`:esbuild 在打包函数时应该视为外部的依赖项数组。这只应用于排除在 Node 之外不会运行的可选依赖项 以下选项适用于 serverless 函数: - `memory`:函数可用的内存量。默认为 `1024` Mb,可以降低到 `128` Mb 或在专业版或企业版账户中以 64Mb 为增量[增加](https://vercel.com/docs/concepts/limits/overview#serverless-function-memory)到 `3008` Mb - `maxDuration`:函数的[最大执行时长](https://vercel.com/docs/functions/runtimes#max-duration)。Hobby 账户默认为 `10` 秒,Pro 为 `15` 秒,Enterprise 为 `900` 秒 - `isr`:配置增量静态重生成,详见下文 如果您的函数需要访问特定区域的数据,建议将它们部署在同一区域(或靠近该区域)以获得最佳性能。 ## 图像优化 您可以设置 `images` 配置来控制 Vercel 如何构建您的图像。完整细节请参见[图像配置参考](https://vercel.com/docs/build-output-api/v3/configuration#images)。例如,您可以设置: ```js // @errors: 2300 2842 7031 1181 1005 1136 1128 /// file: svelte.config.js import adapter from '@sveltejs/adapter-vercel'; export default { kit: { adapter({ images: { sizes: [640, 828, 1200, 1920, 3840], formats: ['image/avif', 'image/webp'], minimumCacheTTL: 300, domains: ['example-app.vercel.app'], } }) } }; ``` ## 增量静态重生成 Vercel 支持[增量静态重生成](https://vercel.com/docs/incremental-static-regeneration) (ISR),它提供了预渲染内容的性能和成本优势,同时保持动态渲染内容的灵活性。 要为路由添加 ISR,在您的 `config` 对象中包含 `isr` 属性: ```js // @errors: 2664 import { BYPASS_TOKEN } from '$env/static/private'; export const config = { isr: { // 缓存资源将通过调用 Serverless 函数重新生成的过期时间(以秒为单位)。 // 将值设置为 `false` 表示永不过期。 expiration: 60, // 可以在 URL 中提供的随机令牌,通过使用 __prerender_bypass= cookie 请求资源来绕过缓存版本。 // // 使用 `x-prerender-revalidate: ` 进行 `GET` 或 `HEAD` 请求将强制重新验证资源。 bypassToken: BYPASS_TOKEN, // 有效查询参数列表。其他参数(如 utm 跟踪代码)将被忽略, // 确保它们不会导致不必要的内容重新生成 allowQuery: ['search'] } }; ``` `expiration` 属性是必需的;其他都是可选的。 > [预渲染](page-options#prerender)的页面将忽略 ISR 配置。 ## 环境变量 Vercel 提供了一组[用于部署的特定的环境变量](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables)。像其他环境变量一样,这些变量可以从 `$env/static/private` 和 `$env/dynamic/private` 访问(有时候 — 稍后会详细说明),并且不能从它们的公共对应项访问。要从客户端访问这些变量之一: ```js // @errors: 2305 /// file: +layout.server.js import { VERCEL_COMMIT_REF } from '$env/static/private'; /** @type {import('./$types').LayoutServerLoad} */ export function load() { return { deploymentGitBranch: VERCEL_COMMIT_REF }; } ``` ```svelte

此暂存环境是从 {data.deploymentGitBranch} 部署的。

``` 由于在 Vercel 上构建时,所有这些变量在构建时和运行时之间都保持不变,我们建议使用 `$env/static/private`(它会静态替换变量,启用死代码消除等优化)而不是 `$env/dynamic/private`。 ## 版本偏差保护 当部署应用的新版本时,之前版本的资源可能不再可访问。如果用户在此时正在使用您的应用,在导航时可能会导致错误 — 这就是所谓的*版本偏差*。SvelteKit 通过检测由版本偏差导致的错误并触发硬重载来获取应用的最新版本来缓解这个问题,但这会导致客户端状态丢失。(您也可以通过观察 [`updated`]($app-stores#updated) store 值来主动缓解它,这个值会告诉客户端何时部署了新版本。) [版本偏差保护](https://vercel.com/docs/deployments/skew-protection)是Vercel 的一个功能,可以将客户端请求路由到它们的原始部署。当用户访问您的应用时,会设置一个带有部署 ID 的 cookie,只要版本偏差保护处于活动状态,任何后续请求都会被路由到该部署。当他们重新加载页面时,将获得最新的部署。(`updated` store 不受此行为影响,因此将继续报告新的部署。)要启用它,请访问 Vercel 上项目设置的高级部分。 基于 cookie 的版本偏差保护有一个注意事项:如果用户在多个标签页中打开了您的应用的多个版本,旧版本的请求将被路由到较新的版本,这意味着它们将回退到 SvelteKit 的内置版本偏差保护。 ## 注意事项 ### Vercel 函数 如果您在项目根目录的 `api` 目录中有 Vercel 函数,任何对 `/api/*` 的请求将*不会*由 SvelteKit 处理。您应该在您的 SvelteKit 应用中将这些实现为 [API 路由](routing#server),除非您需要使用非 JavaScript 语言,在这种情况下您需要确保您的 SvelteKit 应用中没有任何 `/api/*` 路由。 ### Node 版本 在某个日期之前创建的项目可能默认使用比 SvelteKit 当前要求的更旧的 Node 版本。您可以[在项目设置中更改 Node 版本](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version)。 ## 故障排除 ### 访问文件系统 您不能在 edge functions 中使用 `fs`。 您*可以*在 serverless 函数中使用它,但它不会按预期工作,因为文件不会从您的项目复制到您的部署中。相反,使用 `$app/server` 中的 [`read`]($app-server#read) 函数来访问您的文件。`read` 在部署为 edge functions 的路由中不起作用(这在将来可能会改变)。 或者,您可以[预渲染](page-options#prerender)相关路由。 # 编写适配器 如果您偏好的环境还没有适配器,您可以自己构建适配器。我们建议[查看类似平台的适配器源码](https://github.com/sveltejs/kit/tree/main/packages)并将其作为起点进行复制。 适配器包实现以下 API,用于创建一个 `Adapter`: ```js // @errors: 2322 // @filename: ambient.d.ts type AdapterSpecificOptions = any; // @filename: index.js // ---cut--- /** @param {AdapterSpecificOptions} options */ export default function (options) { /** @type {import('@sveltejs/kit').Adapter} */ const adapter = { name: 'adapter-package-name', async adapt(builder) { // 适配器实现 }, async emulate() { return { async platform({ config, prerender }) { // 返回的对象在开发、构建和预览时成为 `event.platform` // 其形状与 `App.Platform` 一致 } } }, supports: { read: ({ config, route }) => { // 如果具有给定 `config` 的路由在生产环境中可以使用来自 // `$app/server` 的 `read`,则返回 `true`,否则返回 `false` // 或抛出描述如何配置部署的描述性错误 } } }; return adapter; } ``` 其中,`name` 和 `adapt` 是必需的。`emulate` 和 `supports` 是可选的。 在 `adapt` 方法中,适配器应该执行以下几个任务: - 清理构建目录 - 使用 `builder.writeClient`、`builder.writeServer` 和 `builder.writePrerendered` 写入 SvelteKit 输出 - 输出以下代码: - 从 `${builder.getServerDirectory()}/index.js` 导入 `Server` - 使用通过 `builder.generateManifest({ relativePath })` 生成的清单实例化应用 - 监听来自平台的请求,必要时将其转换为标准 [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request),调用 `server.respond(request, { getClientAddress })` 函数生成 [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) 并响应 - 通过传递给 `server.respond` 的 `platform` 选项向 SvelteKit 暴露任何平台特定信息 - 如果必要,全局填充 `fetch` 以在目标平台上工作。SvelteKit 为可以使用 `undici` 的平台提供了 `@sveltejs/kit/node/polyfills` 辅助工具 - 如果必要,打包输出以避免需要在目标平台上安装依赖 - 将用户的静态文件和生成的 JS/CSS 放在目标平台的正确位置 在可能的情况下,我们建议将适配器输出放在 `build/` 目录下,将任何中间输出放在 `.svelte-kit/[adapter-name]` 目录下。 # 高级路由 ## 剩余参数 如果路由段的数量未知,您可以使用剩余语法 — 例如您可以像这样实现 GitHub 的文件查看器... ```bash /[org]/[repo]/tree/[branch]/[...file] ``` ...在这种情况下,对 `/sveltejs/kit/tree/main/documentation/docs/04-advanced-routing.md` 的请求将导致以下参数可供页面使用: ```js // @noErrors { org: 'sveltejs', repo: 'kit', branch: 'main', file: 'documentation/docs/04-advanced-routing.md' } ``` > [!NOTE] `src/routes/a/[...rest]/z/+page.svelte` 将匹配 `/a/z`(即完全没有参数)以及 `/a/b/z` 和 `/a/b/c/z` 等。请确保检查剩余参数的值是否有效,例如使用[匹配器](#Matching)。 ### 404 页面 剩余参数还允许您渲染自定义 404。给定这些路由... ```tree src/routes/ ├ marx-brothers/ │ ├ chico/ │ ├ harpo/ │ ├ groucho/ │ └ +error.svelte └ +error.svelte ``` ...如果您访问 `/marx-brothers/karl`,`marx-brothers/+error.svelte` 文件将不会被渲染,因为没有匹配到路由。如果您想渲染嵌套的错误页面,您应该创建一个匹配任何 `/marx-brothers/*` 请求的路由,并从中返回 404: ```tree src/routes/ ├ marx-brothers/ +++| ├ [...path]/+++ │ ├ chico/ │ ├ harpo/ │ ├ groucho/ │ └ +error.svelte └ +error.svelte ``` ```js /// file: src/routes/marx-brothers/[...path]/+page.js import { error } from '@sveltejs/kit'; /** @type {import('./$types').PageLoad} */ export function load(event) { error(404, 'Not Found'); } ``` > [!NOTE] 如果您不处理 404 情况,它们将出现在 [`handleError`](hooks#Shared-hooks-handleError) 中 ## 可选参数 像 `[lang]/home` 这样的路由包含一个名为 `lang` 的必需参数。有时需要让这些参数成为可选的,这样在这个例子中 `home` 和 `en/home` 都指向同一个页面。您可以通过将参数包裹在另一对括号中来实现:`[[lang]]/home` 注意,可选路由参数不能跟在剩余参数后面(`[...rest]/[[optional]]`),因为参数是"贪婪"匹配的,可选参数永远不会被使用。 ## 匹配 像 `src/routes/fruits/[page]` 这样的路由会匹配 `/fruits/apple`,但它也会匹配 `/fruits/rocketship`。我们不希望这样。您可以通过在 [`params`](configuration#files) 目录中添加一个 _匹配器_ — 它接收参数字符串(`"apple"` 或 `"rocketship"`)并在有效时返回 `true` — 来确保路由参数格式正确... ```js /// file: src/params/fruit.js /** * @param {string} param * @return {param is ('apple' | 'orange')} * @satisfies {import('@sveltejs/kit').ParamMatcher} */ export function match(param) { return param === 'apple' || param === 'orange'; } ``` ...并增强您的路由: ``` src/routes/fruits/[page+++=fruit+++] ``` 如果路径名不匹配,SvelteKit 将尝试匹配其他路由(使用下面指定的排序顺序),最终返回 404。 `params` 目录中的每个模块对应一个匹配器,除了 `*.test.js` 和 `*.spec.js` 文件,这些文件可用于对匹配器进行单元测试。 > [!NOTE] 匹配器在服务端和浏览器中都会运行。 ## 排序 多个路由可能匹配同一个路径。例如,以下每个路由都会匹配 `/foo-abc`: ```bash 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 需要知道正在请求哪个路由。为此,它根据以下规则进行排序... - 更具体的路由具有更高的优先级(例如,没有参数的路由比有一个动态参数的路由更具体,以此类推) - 带有[匹配器](#Matching)的参数(`[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` 而不是不太具体的路由: ```bash 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 确定字符的十六进制代码: ```js ':'.charCodeAt(0).toString(16); // '3a',因此使用 '[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 编程](https://unicodebook.readthedocs.io/unicode_encodings.html)。 > [!NOTE] 由于 TypeScript [难以处理](https://github.com/microsoft/TypeScript/issues/13399)以 `.` 字符开头的目录,在创建例如 [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI) 路由时,您可能会发现对这些字符进行编码会很有用:`src/routes/[x+2e]well-known/...` ## 高级布局 默认情况下,_布局层次结构_ 反映了 _路由层次结构_。在某些情况下,这可能不是您想要的。 ### (group) 也许您有一些路由是"应用"路由,应该有一个布局(例如 `/dashboard` 或 `/item`),而其他路由则是"营销"路由,应该有一个不同的布局(`/about` 或 `/testimonials`)。 我们可以用一个括号括起来的目录名称对这些路由进行分组 — 与普通目录不同,`(app)` 和 `(marketing)` 不影响路由的 URL 路径名: ```tree 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` 路由: ```tree 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` ```tree 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 // 使用 (app)/item/[id]/+layout.svelte │ │ │ ├ +layout.svelte // 继承自 (app)/item/+layout@.svelte │ │ │ └ +page.svelte // 使用 (app)/item/+layout@.svelte │ │ └ +layout@.svelte // 继承自根布局,跳过 (app)/+layout.svelte │ └ +layout.svelte └ +layout.svelte ``` ### 何时使用布局分组 并非所有用例都适合使用布局分组,您也不应该觉得必须使用它们。可能您的用例会导致复杂的 `(group)` 嵌套,或者您不想为一个特例引入 `(group)`。使用其他方式如组合(可复用的 `load` 函数或 Svelte 组件)或 if 语句来实现您想要的效果也完全可以。以下示例展示了一个回退到根布局并复用其他布局也可以使用的组件和函数: ```svelte {@render children()} ``` ```js /// file: src/routes/nested/route/+layout.js // @filename: ambient.d.ts declare module "$lib/reusable-load-function" { export function reusableLoad(event: import('@sveltejs/kit').LoadEvent): Promise>; } // @filename: index.js // ---cut--- import { reusableLoad } from '$lib/reusable-load-function'; /** @type {import('./$types').PageLoad} */ export function load(event) { // Add additional logic here, if needed return reusableLoad(event); } ``` ## 进一步阅读 - [教程:高级路由](/tutorial/kit/optional-params) # Hooks “Hooks” 是您声明的应用程序范围的函数,SvelteKit 会在响应特定事件时调用它们,让您能够对框架的行为进行更为精细的控制。 有三个 hook 文件,都是可选的: - `src/hooks.server.js` — 您的应用程序的服务端 hook - `src/hooks.client.js` — 您的应用程序的客户端 hook - `src/hooks.js` — 您的应用程序的在客户端和服务端都运行的 hook 这些模块中的代码会在应用程序启动时运行,这使得它们对初始化数据库客户端等操作很有用。 > [!NOTE] 您可以通过 [`config.kit.files.hooks`](configuration#files) 配置这些文件的位置。 ## 服务端 hook 以下 hook 可以添加到 `src/hooks.server.js` 中: ### handle 这个函数在 SvelteKit 服务端每次接收到 [request](web-standards#Fetch-APIs-Request) 时运行 — 无论是在应用程序运行时,还是在[预渲染](page-options#prerender)过程中 — 并决定[response](web-standards#Fetch-APIs-Response)。 它接收一个表示请求的 `event` 对象和一个名为 `resolve` 的函数,该函数渲染路由并生成一个 `Response`。这允许您修改响应头或响应体,或完全绕过 SvelteKit(例如,用于以编程方式实现路由)。 ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { if (event.url.pathname.startsWith('/custom')) { return new Response('custom response'); } const response = await resolve(event); return response; } ``` > [!NOTE] 对静态资源的请求 — 包括已经预渲染的页面 — 不会由 SvelteKit 处理。 如果未实现,默认为 `({ event, resolve }) => resolve(event)`。 ### locals 要向请求中添加自定义数据(这些数据会传递给 `+server.js` 中的处理程序和服务端的 `load` 函数),可以填充 `event.locals` 对象,如下所示。 ```js /// file: src/hooks.server.js // @filename: ambient.d.ts type User = { name: string; } declare namespace App { interface Locals { user: User; } } const getUserInformation: (cookie: string | void) => Promise; // @filename: index.js // ---cut--- /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { event.locals.user = await getUserInformation(event.cookies.get('sessionid')); const response = await resolve(event); response.headers.set('x-custom-header', 'potato'); return response; } ``` 您可以定义多个 `handle` 函数,并使用[`sequence` 辅助函数](@sveltejs-kit-hooks)执行它们。 `resolve` 还支持第二个可选参数,让您能够更好地控制响应的渲染方式。该参数是一个对象,可以包含以下字段: - `transformPageChunk(opts: { html: string, done: boolean }): MaybePromise` — 对 HTML 应用自定义转换。如果 `done` 为 true,则是最后一个块。块不保证是格式良好的 HTML(例如,它们可能包含一个元素的开始标签但没有结束标签),但它们总是会在合理的边界处分割,比如 `%sveltekit.head%` 或布局/页面组件。 - `filterSerializedResponseHeaders(name: string, value: string): boolean` — 确定当 `load` 函数使用 `fetch` 加载资源时,哪些头部应该包含在序列化的响应中。默认情况下,不会包含任何头部。 - `preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean` — 确定应该在 `` 标签中添加哪些文件以预加载。该方法在构建代码块时被调用,每个找到的文件都会被调用 — 例如,如果您在 `+page.svelte` 中有 `import './styles.css`,在访问该页面时,`preload` 将传入该 CSS 文件的解析路径进行调用。注意,在开发模式下不会调用 `preload`,因为它依赖于构建时的分析。预加载可以通过更早下载资源来提高性能,但如果不必要地下载太多内容也会适得其反。默认情况下,会预加载 `js` 和 `css` 文件。目前不会预加载 `asset` 文件,但我们可能会在评估反馈后添加此功能。 ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').Handle} */ export async function handle({ event, resolve }) { const response = await resolve(event, { transformPageChunk: ({ html }) => html.replace('old', 'new'), filterSerializedResponseHeaders: (name) => name.startsWith('x-'), preload: ({ type, path }) => type === 'js' || path.includes('/important/') }); return response; } ``` 注意,`resolve(...)` 永远不会抛出错误,它总是会返回一个带有适当状态码的 `Promise`。如果在 `handle` 期间其他地方抛出错误,这将被视为致命错误,SvelteKit 将根据 `Accept` 头部返回错误的 JSON 表示或回退错误页面 — 后者可以通过 `src/error.html` 自定义。您可以在[这里](errors)阅读更多关于错误处理的信息。 ### handleFetch 这个函数允许您修改(或替换)在服务端上运行的 `load` 或 `action` 函数中发生的 `fetch` 请求(或在预渲染期间)。 例如,当用户执行客户端导航到相应页面时,您的 `load` 函数可能会向公共 URL(如 `https://api.yourapp.com`)发出请求,但在 SSR 期间,直接访问 API 可能更有意义(绕过位于它和公共互联网之间的代理和负载均衡器)。 ```js /// file: src/hooks.server.js /** @type {import('@sveltejs/kit').HandleFetch} */ export async function handleFetch({ request, fetch }) { if (request.url.startsWith('https://api.yourapp.com/')) { // 克隆原始请求,但改变 URL request = new Request( request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'), request ); } return fetch(request); } ``` **认证凭据** 对于同源请求,除非 `credentials` 选项设置为 `"omit"`,否则 SvelteKit 的 `fetch` 实现会转发 `cookie` 和 `authorization` 头部。 对于跨源请求,如果请求 URL 属于应用程序的子域,则会包含 `cookie` — 例如,如果您的应用程序在 `my-domain.com` 上,而您的 API 在 `api.my-domain.com` 上,cookie 将包含在请求中。 如果您的应用程序和 API 在兄弟子域上 — 例如 `www.my-domain.com` 和 `api.my-domain.com` — 那么属于共同父域(如 `my-domain.com`)的 cookie 将不会被包含,因为 SvelteKit 无法知道 cookie 属于哪个域。在这些情况下,您需要使用 `handleFetch` 手动包含 cookie: ```js /// file: src/hooks.server.js // @errors: 2345 /** @type {import('@sveltejs/kit').HandleFetch} */ export async function handleFetch({ event, request, fetch }) { if (request.url.startsWith('https://api.my-domain.com/')) { request.headers.set('cookie', event.request.headers.get('cookie')); } return fetch(request); } ``` ## 共享 hook 以下 hook 可以同时添加到 `src/hooks.server.js` 和 `src/hooks.client.js` 中: ### handleError 如果在加载或渲染期间抛出[意外错误](errors#Unexpected-errors),此函数将被调用,并传入 `error`、`event`、`status` 代码和 `message`。这允许两件事: - 您可以记录错误 - 您可以生成一个安全的、显示给用户的自定义错误表示,省略敏感的详细信息,如消息和堆栈跟踪。返回的值(默认为 `{ message }`)会成为 `$page.error` 的值。 对于从您的代码(或您的代码调用的库代码)抛出的错误,状态将为 500,消息将为 "Internal Error"。虽然 `error.message` 可能包含不应暴露给用户的敏感信息,但 `message` 是安全的(尽管对普通用户来说没有意义)。 要以类型安全的方式向 `$page.error` 对象添加更多信息,您可以通过声明 `App.Error` 接口(必须包含 `message: string`,以保证合理的回退行为)来自定义预期的形状。这允许您 — 例如 — 附加一个跟踪 ID,供用户在与技术支持人员通信时引用: ```ts /// file: src/app.d.ts declare global { namespace App { interface Error { message: string; errorId: string; } } } export {}; ``` ```js /// file: src/hooks.server.js // @errors: 2322 2353 // @filename: ambient.d.ts declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js // ---cut--- import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleServerError} */ export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); // 与 https://sentry.io/ 集成的示例 Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: '哎呀!', errorId }; } ``` ```js /// file: src/hooks.client.js // @errors: 2322 2353 // @filename: ambient.d.ts declare module '@sentry/sveltekit' { export const init: (opts: any) => void; export const captureException: (error: any, opts: any) => void; } // @filename: index.js // ---cut--- import * as Sentry from '@sentry/sveltekit'; Sentry.init({/*...*/}) /** @type {import('@sveltejs/kit').HandleClientError} */ export async function handleError({ error, event, status, message }) { const errorId = crypto.randomUUID(); // 与 https://sentry.io/ 集成的示例 Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: '哎呀!', errorId }; } ``` > [!NOTE] 在 `src/hooks.client.js` 中,`handleError` 的类型是 `HandleClientError` 而不是 `HandleServerError`,并且 `event` 是一个 `NavigationEvent` 而不是 `RequestEvent`。 此函数不会因为预期的错误(那些使用从 `@sveltejs/kit` 导入的 [`error`](@sveltejs-kit#error) 函数抛出的错误)而被调用。 在开发过程中,如果由于 Svelte 代码中的语法错误而发生错误,传入的错误会附加一个 `frame` 属性,突出显示错误的位置。 > [!NOTE] 确保 `handleError` 永远不会抛出错误 ### init 这个函数在服务端创建或应用程序在浏览器中启动时运行一次,是执行异步工作(如初始化数据库连接)的有用位置。 > [!NOTE] 如果您的环境支持顶级 await,`init` 函数实际上与在模块顶层编写初始化逻辑没有什么不同,但一些环境 — 尤其是 Safari — 不支持。 ```js /// file: src/hooks.server.js import * as db from '$lib/server/database'; /** @type {import('@sveltejs/kit').ServerInit} */ export async function init() { await db.connect(); } ``` > [!NOTE] > 在浏览器中,`init` 中的异步工作会延迟水合,所以要注意您在那里放什么。 ## 通用 hook 以下 hook 可以添加到 `src/hooks.js` 中。通用 hook 在服务端和客户端都运行(不要与共享 hook 混淆,后者是特定环境的)。 ### reroute 这个函数在 `handle` 之前运行,允许您更改 URL 如何转换为路由。返回的路径名(默认为 `url.pathname`)用于选择路由及其参数。 例如,您可能有一个 `src/routes/[[lang]]/about/+page.svelte` 页面,它应该可以访问为 `/en/about` 或 `/de/ueber-uns` 或 `/fr/a-propos`。您可以用 `reroute` 来实现: ```js /// file: src/hooks.js // @errors: 2345 // @errors: 2304 /** @type {Record} */ const translated = { '/en/about': '/en/about', '/de/ueber-uns': '/de/about', '/fr/a-propos': '/fr/about' }; /** @type {import('@sveltejs/kit').Reroute} */ export function reroute({ url }) { if (url.pathname in translated) { return translated[url.pathname]; } } ``` `lang` 参数将从返回的路径名正确派生。 使用 `reroute` 不会改变浏览器地址栏的内容,也不会改变 `event.url` 的值。 ### 传输 这是一组 _传输器_,允许您跨服务端/客户端边界传递自定义类型 - 从 `load` 和 form actions 返回的类型。每个传输器都包含一个 `encode` 函数,该函数对服务端上的值进行编码(或对任何不是该类型的实例返回 false),以及一个相应的 `decode` 函数: ```js /// file: src/hooks.js import { Vector } from '$lib/math'; /** @type {import('@sveltejs/kit').Transport} */ export const transport = { Vector: { encode: (value) => value instanceof Vector && [value.x, value.y], decode: ([x, y]) => new Vector(x, y) } }; ``` ## 进一步阅读 - [教程: Hooks](/tutorial/kit/handle) # 错误处理 错误是软件开发中不可避免的事实。SvelteKit 根据错误发生的位置、错误类型以及传入请求的性质,采用不同的方式处理错误。 ## 错误对象 SvelteKit 区分预期错误和意外错误,默认情况下这两种错误都表示为简单的 `{ message: string }` 对象。 您可以添加额外的属性,比如 `code` 或跟踪 `id`,如下面的示例所示。(使用 TypeScript 时,这需要您重新定义 `Error` 类型,如 [类型安全](errors#Type-safety) 中所述)。 ## 预期错误 预期错误是使用从 `@sveltejs/kit` 导入的 [`error`](@sveltejs-kit#error) 辅助函数创建的错误: ```js /// file: src/routes/blog/[slug]/+page.server.js // @filename: ambient.d.ts declare module '$lib/server/database' { export function getPost(slug: string): Promise<{ title: string, content: string } | undefined> } // @filename: index.js // ---cut--- import { error } from '@sveltejs/kit'; import * as db from '$lib/server/database'; /** @type {import('./$types').PageServerLoad} */ export async function load({ params }) { const post = await db.getPost(params.slug); if (!post) { error(404, { message: '未找到' }); } return { post }; } ``` 这会抛出一个异常,SvelteKit 会捕获该异常,并将响应状态码设置为 404,并渲染一个 [`+error.svelte`](routing#error) 组件,其中 `page.error` 是一个对象,提供给 `error(...)` 的第二个参数。 ```svelte

{page.error.message}

``` > [!LEGACY] > `$app/state` 是在 SvelteKit 2.12 中添加的。如果您使用的是早期版本或正在使用 Svelte 4,请使用 `$app/stores` 代替。 如果需要,您可以向错误对象添加额外的属性... ```js import { error } from '@sveltejs/kit'; declare global { namespace App { interface Error { message: string; code: string; } } } // ---cut--- error(404, { message: '未找到', +++code: 'NOT_FOUND'+++ }); ``` ...否则,为了方便起见,您可以将字符串作为第二个参数传递: ```js import { error } from '@sveltejs/kit'; // ---cut--- ---error(404, { message: '未找到' });--- +++error(404, '未找到');+++ ``` > [!NOTE] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you),您必须自己 `throw` 这个 `error` ## 意外错误 意外错误是处理请求时发生的任何其他异常。由于这些错误可能包含敏感信息,意外错误消息和堆栈跟踪不会暴露给用户。 默认情况下,意外错误会打印到控制台(或在生产环境中打印到服务端日志),而暴露给用户的错误具有通用的形状: ```json { "message": "内部错误" } ``` 意外错误将通过 [`handleError`](hooks#Shared-hooks-handleError) hook 处理,在那里您可以添加自己的错误处理逻辑 — 例如,将错误发送到报告服务,或返回一个自定义错误对象,该对象将成为 `$page.error`。 ## 响应 如果错误发生在 `handle` 或 [`+server.js`](routing#server) 请求处理程序内部,SvelteKit 将根据请求的 `Accept` 头响应一个回退错误页面或错误对象的 JSON 表示。 您可以通过添加 `src/error.html` 文件来自定义回退错误页面: ```html %sveltekit.error.message%

我的自定义错误页面

状态:%sveltekit.status%

消息:%sveltekit.error.message%

``` SvelteKit 将用相应的值替换 `%sveltekit.status%` 和 `%sveltekit.error.message%`。 如果错误发生在渲染页面时的 `load` 函数内部,SvelteKit 将渲染最接近错误发生位置的 [`+error.svelte`](routing#error) 组件。如果错误发生在 `+layout(.server).js` 中的 `load` 函数内部,最近的错误边界是该布局之上的 `+error.svelte` 文件(不是在它旁边)。 例外情况是当错误发生在根 `+layout.js` 或 `+layout.server.js` 内部时,因为根布局通常会包含 `+error.svelte` 组件。在这种情况下,SvelteKit 使用回退错误页面。 ## 类型安全 如果您使用 TypeScript 并需要自定义错误的形状,您可以通过在您的应用程序中声明一个 `App.Error` 接口来实现(按照惯例,在 `src/app.d.ts` 中,尽管它可以存在于 TypeScript 可以"看到"的任何地方): ```ts /// file: src/app.d.ts declare global { namespace App { interface Error { +++ code: string; id: string;+++ } } } export {}; ``` 此接口始终包含 `message: string` 属性。 ## 进一步阅读 - [教程:错误和重定向](/tutorial/kit/error-basics) - [教程:Hooks ](/tutorial/kit/handle) # 链接选项 在 SvelteKit 中,使用 `
` 元素(而不是框架特定的 `` 组件)在应用程序的路由之间导航。如果用户点击一个由应用程序"拥有"的链接(而不是指向外部网站的链接),SvelteKit 将通过导入其代码并调用任何所需的 `load` 函数来获取数据,从而导航到新页面。 您可以使用 `data-sveltekit-*` 属性来自定义链接的行为。这些属性可以应用于 `` 本身,或者应用于父元素。 这些选项也适用于具有 [`method="GET"`](form-actions#GET-vs-POST) 的 `` 元素。 ## data-sveltekit-preload-data 在浏览器注册到用户点击链接之前,我们可以检测到他们已经将鼠标悬停在链接上(在桌面端),或者触发了 `touchstart` 或 `mousedown` 事件。在这两种情况下,我们可以做出明智的猜测,即 `click` 事件即将到来。 SvelteKit 可以利用这些信息来提前开始导入代码和获取页面数据,这可以为我们多争取几百毫秒的时间 —— 这就是用户界面感觉迟缓和感觉流畅之间的差异。 我们可以通过 `data-sveltekit-preload-data` 属性来控制这种行为,该属性可以是以下两个值之一: - `"hover"` 表示当鼠标在链接上停留时将开始预加载。在移动设备上,预加载在 `touchstart` 时开始 - `"tap"` 表示一旦注册到 `touchstart` 或 `mousedown` 事件就会开始预加载 默认项目模板在 `src/app.html` 中的 `` 元素上应用了 `data-sveltekit-preload-data="hover"` 属性,这意味着默认情况下每个链接都会在悬停时预加载: ```html
%sveltekit.body%
``` 有时候,当用户悬停在链接上时调用 `load` 可能不太理想,要么是因为这可能会导致误判(悬停后不一定会点击),要么是因为数据更新非常快,延迟可能意味着数据已经过时。 在这些情况下,您可以指定 `"tap"` 值,这会导致 SvelteKit 仅在用户点击链接时调用 `load`: ```html
获取当前股票价值 ``` > [!NOTE] 您也可以从 `$app/navigation` 导入 `preloadData`,以编程的方式调用。 如果用户设置了减少数据使用(即 [`navigator.connection.saveData`](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/saveData) 为 `true`),数据将永远不会被预加载。 ## data-sveltekit-preload-code 即使在您不想为链接预加载*数据*的情况下,预加载*代码*也可能是有益的。`data-sveltekit-preload-code` 属性的工作方式类似于 `data-sveltekit-preload-data`,但它可以接受四个值之一,按"急切"递减排序: - `"eager"` 表示链接将立即预加载 - `"viewport"` 表示链接一旦进入视口就会预加载 - `"hover"` - 如上所述,但只预加载代码 - `"tap"` - 如上所述,但只预加载代码 注意,`viewport` 和 `eager` 仅适用于导航后立即存在于 DOM 中的链接 —— 如果链接是稍后添加的(例如在 `{#if ...}` 块中),则在触发 `hover` 或 `tap` 之前不会预加载。这是为了避免因过度观察 DOM 变化而导致的性能问题。 > [!NOTE] 由于预加载代码是预加载数据的先决条件,因此只有当此属性指定的值比存在的任何 `data-sveltekit-preload-data` 属性更急切时,此属性才会生效。 与 `data-sveltekit-preload-data` 一样,如果用户选择了减少数据使用,此属性将被忽略。 ## data-sveltekit-reload 偶尔,我们需要告诉 SvelteKit 不要处理链接,而是让浏览器来处理。给链接添加 `data-sveltekit-reload` 属性... ```html 路径 ``` ...将会在点击链接时导致完整的页面导航。 具有 `rel="external"` 属性的链接将受到相同的处理。此外,它们在[预渲染](page-options#prerender)期间会被忽略。 ## data-sveltekit-replacestate 有时您不希望导航在浏览器的会话历史记录中创建新条目。给链接添加 `data-sveltekit-replacestate` 属性... ```html 路径 ``` ...在点击链接时将替换当前的 `history` 条目,而不是用 `pushState` 创建新的条目。 ## data-sveltekit-keepfocus 有时您不希望在导航后[重置焦点](accessibility#Focus-management)。例如,也许您有一个在用户输入时就提交的搜索表单,您想保持文本输入框的焦点。给它添加 `data-sveltekit-keepfocus` 属性... ```html
``` ...将导致当前获得焦点的元素在导航后保持焦点。通常,应该避免在链接上使用此属性,因为获得焦点的元素将是 `` 标签(而不是之前获得焦点的元素),并且屏幕阅读器和其他辅助技术的用户通常期望在导航后移动焦点。您还应该只在导航后仍然存在的元素上使用此属性。如果元素不再存在,用户的焦点将会丢失,这会让使用辅助技术的用户感到困惑。 ## data-sveltekit-noscroll 在导航到内部链接时,SvelteKit 会模仿浏览器的默认导航行为:它会将滚动位置改变到 0,0,使用户处于页面的最左上角(除非链接包含 `#hash`,在这种情况下,它会滚动到具有匹配 ID 的元素)。 在某些情况下,您可能希望禁用这种行为。给链接添加 `data-sveltekit-noscroll` 属性... ```html 路径 ``` ...将在点击链接后阻止滚动。 ## 禁用选项 要在启用了这些选项的元素内禁用任何选项,请使用 `"false"` 值: ```html
a b c
d e f
``` 要有条件地将属性应用于元素,请这样做: ```svelte
``` # Service workers Service workers 作为代理服务端,处理应用程序内部的网络请求。这使得您的应用程序能够离线工作,但即使您不需要离线支持(或由于您正在构建的应用程序类型而无法实现它),使用 service workers 预缓存构建的 JS 和 CSS 来加快导航速度,通常也是值得的。 在 SvelteKit 中,如果您有一个 `src/service-worker.js` 文件(或 `src/service-worker/index.js`),它将被打包并自动注册。如果需要,您可以更改 [service worker 的位置](configuration#files)。 如果您需要使用自己的逻辑注册 service worker 或使用其他解决方案,可以[禁用自动注册](configuration#serviceWorker)。默认注册看起来类似这样: ```js if ('serviceWorker' in navigator) { addEventListener('load', function () { navigator.serviceWorker.register('./path/to/service-worker.js'); }); } ``` ## Service Worker 内部 在 service worker 内部,您可以访问 [`$service-worker` 模块]($service-worker),它为您提供所有静态资源、构建文件和预渲染页面的路径。您还会获得一个应用程序版本字符串,可用于创建唯一的缓存名称,以及部署的 `base` 路径。如果您的 Vite 配置指定了 `define`(用于全局变量替换),这也将应用于 service workers 以及服务端/客户端构建。 以下示例会尽可能早的缓存构建的应用程序和 `static` 中的所有文件,并在访问时缓存所有其他请求。这将使每个页面在访问后都能离线工作。 ```js // @errors: 2339 /// import { build, files, version } from '$service-worker'; // 为此部署创建唯一的缓存名称 const CACHE = `cache-${version}`; const ASSETS = [ ...build, // 应用程序本身 ...files // `static` 中的所有内容 ]; self.addEventListener('install', (event) => { // 创建新缓存并添加所有文件 async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); } event.waitUntil(addFilesToCache()); }); self.addEventListener('activate', (event) => { // 从磁盘删除以前的缓存数据 async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } } event.waitUntil(deleteOldCaches()); }); self.addEventListener('fetch', (event) => { // 忽略 POST 请求等 if (event.request.method !== 'GET') return; async function respond() { const url = new URL(event.request.url); const cache = await caches.open(CACHE); // `build`/`files` 始终可以从缓存中提供服务 if (ASSETS.includes(url.pathname)) { const response = await cache.match(url.pathname); if (response) { return response; } } // 对于其他所有内容,首先尝试网络 // 但如果我们离线,则回退到缓存 try { const response = await fetch(event.request); // 如果我们离线,fetch 可能返回非 Response 值 // 而不是抛出错误 - 我们不能将这个非 Response 传递给 respondWith if (!(response instanceof Response)) { throw new Error('invalid response from fetch'); } if (response.status === 200) { cache.put(event.request, response.clone()); } return response; } catch (err) { const response = await cache.match(event.request); if (response) { return response; } // 如果没有缓存,就直接报错 // 因为我们无法对这个请求做任何响应 throw err; } } event.respondWith(respond()); }); ``` > [!NOTE] 缓存时要小心!在某些情况下,过时的数据可能比离线时无法获取的数据更糟糕。由于浏览器会在缓存太满时清空缓存,因此您还应该谨慎缓存大型资源,如视频文件。 ## 在开发过程中 service worker 在生产环境中会被打包,但在开发过程中不会。因此,只有支持 [service workers 中的模块](https://web.dev/es-modules-in-sw) 的浏览器才能在开发时使用它们。如果您手动注册 service worker,在开发时需要传递 `{ type: 'module' }` 选项: ```js import { dev } from '$app/environment'; navigator.serviceWorker.register('/service-worker.js', { type: dev ? 'module' : 'classic' }); ``` > [!NOTE] 在开发环境中,`build` 和 `prerendered` 是空数组 ## 类型安全 为 service workers 设置适当的类型需要一些手动设置。在您的 `service-worker.js` 中,在文件顶部添加以下内容: ```original-js /// /// /// /// const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self)); ``` ```generated-ts /// /// /// /// const sw = self as unknown as ServiceWorkerGlobalScope; ``` 这会禁用对 service worker 中不可用的 DOM 类型(如 `HTMLElement`)的访问,并实例化正确的全局变量。将 `self` 重新赋值给 `sw` 允许您在此过程中进行类型转换(有几种方法可以做到这一点,但这是最简单的,不需要额外的文件)。在文件的其余部分使用 `sw` 而不是 `self`。 对 SvelteKit 类型的引用确保 `$service-worker` 导入具有适当的类型定义。如果您导入 `$env/static/public`,您要么必须使用 `// @ts-ignore` 注释导入,要么添加 `/// ` 到引用类型中。 ## 其他解决方案 SvelteKit 的 service worker 实现故意保持低级别。如果您需要更全功能但也更有主见的解决方案,我们建议查看像 [Vite PWA 插件](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html) 这样的解决方案,它使用 [Workbox](https://web.dev/learn/pwa/workbox)。有关 service workers 的更多一般信息,我们推荐 [MDN web 文档](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)。 # 仅服务端模块 SvelteKit 会像一个好朋友一样,保守您的秘密。在同一个代码仓库中编写后端和前端代码时,很容易不小心将敏感数据(例如包含 API 密钥的环境变量)导入到前端代码中。SvelteKit 提供了一种完全防止这种情况发生的方法:仅服务端模块(server-only modules)。 ## 私有环境变量 [`$env/static/private`]($env-static-private) 和 [`$env/dynamic/private`]($env-dynamic-private) 模块只能导入到仅在服务端上运行的模块中,例如 [`hooks.server.js`](hooks#Server-hooks) 或 [`+page.server.js`](routing#page-page.server.js)。 ## 仅服务端工具 [`$app/server`]($app-server) 模块包含一个 [`read`]($app-server#read) 函数,用于从文件系统读取资源,同样只能被服务端运行的代码导入。 ## 您的模块 您可以通过两种方式使您的模块成为仅服务端模块: - 在文件名中添加 `.server`,例如 `secrets.server.js` - 将它们放在 `$lib/server` 中,例如 `$lib/server/secrets.js` ## 工作原理 任何时候,当您有公开的代码,导入仅服务端代码时(无论是直接还是间接)... ```js // @errors: 7005 /// file: $lib/server/secrets.js export const atlantisCoordinates = [ /* 已编辑 */ ]; ``` ```js // @errors: 2307 7006 7005 /// file: src/routes/utils.js export { atlantisCoordinates } from '$lib/server/secrets.js'; export const add = (a, b) => a + b; ``` ```html /// file: src/routes/+page.svelte ``` ...SvelteKit 将报错: ``` Cannot import $lib/server/secrets.js into public-facing code: - src/routes/+page.svelte - src/routes/utils.js - $lib/server/secrets.js ``` 尽管公开代码 — `src/routes/+page.svelte` — 只使用了 `add` 导出而没有使用秘密的 `atlantisCoordinates` 导出,秘密代码也可能最终出现在浏览器下载的 JavaScript 中,因此这个导入链被认为是不安全的。 此功能也适用于动态导入,甚至是像 ``await import(`./${foo}.js`)`` 这样的插值导入,只有一个小注意点:在开发过程中,如果公开代码和仅服务端模块之间存在两个或更多的动态导入,则在第一次加载代码时不会检测到非法导入。 > [!NOTE] 像 Vitest 这样的单元测试框架不区分仅服务端代码和公开代码。因此,当运行测试时,非法导入检测会被禁用,这由 `process.env.TEST === 'true'` 决定。 ## 进一步阅读 - [教程:环境变量](/tutorial/kit/env-static-private) # 快照 临时的 DOM 状态 — 比如侧边栏的滚动位置、`` 元素的内容等 — 在从一个页面导航到另一个页面时会被丢弃。 例如,如果用户填写了一个表单但在提交之前离开并返回,或者用户刷新页面,他们填写的值将会丢失。在需要保留这些输入的情况下,您可以创建一个 DOM 状态的*快照*,当用户返回时可以恢复这个状态。 要实现这一点,从 `+page.svelte` 或 `+layout.svelte` 中导出一个带有 `capture` 和 `restore` 方法的 `snapshot` 对象: ```svelte