This is the developer documentation for SvelteKit. # Introduction ## 在我们开始之前 > \[!注意\] 如果你是 Svelte 或 SvelteKit 的新手,我们建议查看[交互式教程](/tutorial/kit)。 > > 如果您遇到困难,请在[Discord 聊天室](/chat)寻求帮助。 ## 什么是 SvelteKit? SvelteKit 是一个使用 [Svelte](../svelte) 快速开发强大、高性能 Web 应用的框架。如果你来自 React,SvelteKit 与 Next 类似。如果你来自 Vue,SvelteKit 与 Nuxt 类似。 要了解更多关于您可以使用 SvelteKit 构建的应用类型,请参阅有关项目类型的[文档](project-types)。 ## 什么是 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)页面;[可配置渲染](page-options),通过[SSR](glossary#SSR)在服务器上处理您的应用程序的不同部分,通过[客户端渲染](glossary#CSR)在浏览器中,或在构建时通过[预渲染](glossary#Prerendering);[图像优化](images);等等。构建一个遵循所有现代最佳实践的应用程序非常复杂,但 SvelteKit 为您处理所有无聊的事情,让您可以专注于创意部分。 它通过利用[Vite](https://vitejs.dev/)和[Svelte 插件](https://github.com/sveltejs/vite-plugin-svelte)进行[热模块替换(HMR)](https://github.com/sveltejs/vite-plugin-svelte/blob/main/docs/config.md#hot),即时反映您在浏览器中对代码的更改,以提供快速且功能丰富的开发体验。 # Creating a project 最简单开始构建 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)。 # Project types SvelteKit 提供可配置的渲染,允许您以多种方式构建和部署项目。您可以使用 SvelteKit 构建以下所有类型的应用程序以及更多。渲染设置不是互斥的,您可以选择以最佳方式渲染应用程序的不同部分。 如果您没有特定的方式来构建您的应用程序,请不要担心!您的应用程序的构建、部署和渲染方式由您选择的适配器以及少量配置控制,这些都可以稍后更改。无论您选择哪种项目类型,[项目结构](project-structure)和[路由](glossary#Routing)都将保持不变。 ## 默认渲染 默认情况下,当用户访问网站时,SvelteKit 将使用[服务器端渲染(SSR)](glossary#SSR)渲染第一个页面,并使用[客户端渲染(CSR)](glossary#CSR)渲染后续页面。使用 SSR 进行初始渲染可以提高 SEO 和初始页面加载的感知性能。然后客户端渲染接管并更新页面,无需重新渲染常见组件,这通常更快,并且在页面间导航时消除了闪烁。使用这种混合渲染方法构建的应用程序也被称为[过渡应用程序](https://www.youtube.com/watch?v=860d8usGC0o)。 ## 静态站点生成 您可以使用 SvelteKit 作为[静态站点生成器(SSG)](glossary#SSG),它使用[`adapter-static`](adapter-static)通过静态渲染完全[预渲染](glossary#Prerendering)您的站点。您还可以使用[预渲染选项](page-options#prerender)仅预渲染一些页面,然后选择不同的适配器以动态服务器渲染其他页面。 工具仅用于静态站点生成可能更有效地在构建过程中扩展预渲染过程,当渲染大量页面时。当处理非常大的静态生成站点时,如果您使用[增量静态重生成(ISR)](adapter-vercel#Incremental-Static-Regeneration),可以避免长时间构建时间。与专门构建的 SSG 相比,SvelteKit 允许在不同页面上混合和匹配不同的渲染类型。 ## 单页应用程序 [单页应用程序(SPAs)](glossary#SPA)仅使用[客户端渲染(CSR)](glossary#CSR)。您可以使用 SvelteKit[构建单页应用程序(SPAs)](single-page-apps)。与所有类型的 SvelteKit 应用程序一样,您可以在 SvelteKit 中编写后端或[其他语言或框架](#Separate-backend)。如果您正在构建没有后端或[独立后端](#Separate-backend)的应用程序,您可以简单地跳过并忽略文档中关于`服务器`文件的讨论。 ## 多页面应用 SvelteKit 通常不用于构建[传统多页应用](glossary#MPA)。然而,在 SvelteKit 中,您可以通过[`csr = false`](page-options#csr)移除页面上的所有 JavaScript,这将使后续链接在服务器上渲染,或者您可以使用[`data-sveltekit-reload`](link-options#data-sveltekit-reload)在服务器上渲染特定链接。 ## 分离后端 如果您的后端是用其他语言编写的,例如 Go、Java、PHP、Ruby、Rust 或 C#,您有几种方法可以部署您的应用程序。最推荐的方式是将您的 SvelteKit 前端与后端分开部署,使用`adapter-node`或无服务器适配器。一些用户更喜欢不使用单独的进程来管理和决定将应用程序作为由后端服务器提供的[单页应用程序(SPA)](single-page-apps)进行部署,但请注意,单页应用程序的 SEO 和性能特性较差。 如果您使用外部后端,可以简单地跳过并忽略文档中关于`服务器`文件的说明。您可能还需要参考[有关如何调用单独后端的常见问题解答](faq#How-do-I-use-a-different-backend-API-server)。 ## 无服务器应用 SvelteKit 应用程序在无服务器平台上运行简单。默认零配置适配器将自动在多个支持的平台运行您的应用程序,或者您可以使用[适配器-vercel](adapter-auto)、[`adapter-netlify`](adapter-vercel)、[`adapter-cloudflare`](adapter-netlify)来提供特定平台的配置。此外,[社区适配器](https://sveltesociety.dev/packages?category=sveltekit-adapters)允许您将应用程序部署到几乎任何无服务器环境中。其中一些适配器,如[`适配器-vercel`](adapter-vercel)和[`适配器-netlify`](adapter-netlify),提供`边缘`选项,以支持[边缘渲染](glossary#Edge)以改善延迟。 ## 您的服务器 您可以使用[`adapter-node`](adapter-node)部署到您自己的服务器或 VPS。 ## 容器 您可以使用[`adapter-node`](adapter-node)在 Docker 或 LXC 等容器中运行 SvelteKit 应用。 ## 图书馆 您可以使用[`@sveltejs/package`](packaging)插件通过在运行[`sv create`](/docs/cli/sv-create)时选择库选项来创建一个可以被其他 Svelte 应用使用的库。 ## 离线应用 SvelteKit 完全支持 [服务工作者](service-workers),让您能够构建多种类型的应用,例如离线应用和 [渐进式 Web 应用](glossary#PWA)。 ## 移动应用 您可以使用[SvelteKit SPA](single-page-apps)通过[Tauri](https://v2.tauri.app/start/frontend/sveltekit/)或[Capacitor](https://capacitorjs.com/solution/svelte)将其转换为移动应用。两种平台都可通过插件提供移动功能,如相机、地理位置和推送通知。 这些移动开发平台通过启动本地 Web 服务器并在手机上像静态主机一样提供服务来工作。您可能会发现[`bundleStrategy: 'single'`](configuration#output)是一个有助于限制请求数量的有用选项。例如,在撰写本文时,Capacitor 本地服务器使用 HTTP/1,这限制了并发连接的数量。 ## 桌面应用程序 您可以使用[SvelteKit SPA](single-page-apps)与[Tauri](https://v2.tauri.app/start/frontend/sveltekit/)、[Wails](https://wails.io/docs/guides/sveltekit/)或[Electron](https://www.electronjs.org/)将其转换为桌面应用程序。 ## 浏览器扩展 您可以使用[`adapter-static`](adapter-static)或[社区适配器](https://sveltesociety.dev/packages?category=sveltekit-adapters)来构建针对浏览器扩展的特定适配器。 ## 嵌入式设备 由于其高效的渲染,Svelte 可以在低功耗设备上运行。嵌入式设备如微控制器和电视可能会限制并发连接的数量。为了减少并发请求数量,您可能会发现在此部署配置中[`bundleStrategy: 'single'`](configuration#output)是一个有用的选项。 # Project structure 一个典型的 SvelteKit 项目看起来是这样的: ```tree my-project/ ├ src/ │ ├ lib/ │ │ ├ server/ │ │ │ └ [your server-only lib files] │ │ └ [your lib files] │ ├ params/ │ │ └ [your param matchers] │ ├ routes/ │ │ └ [your routes] │ ├ app.html │ ├ error.html │ ├ hooks.client.js │ ├ hooks.server.js │ └ service-worker.js ├ static/ │ └ [your static assets] ├ tests/ │ └ [your tests] ├ package.json ├ svelte.config.js ├ tsconfig.json └ vite.config.js ``` 您还将找到常见的文件,如`.gitignore`和`.npmrc`(以及`.prettierrc`和`eslint.config.js`等等,如果您在运行`npx sv create`时选择了这些选项)。 ## 项目文件 ### src The `src` 目录包含您项目的核心内容。除了 `src/routes` 和 `src/app.html` 之外的所有内容都是可选的。 * `lib` 包含您的库代码(实用工具和组件),可以通过 [`$lib`]($lib) 别名导入,或者使用 [`svelte-package`](packaging) 打包进行分发 * `服务器` 包含您的服务器专用库代码。您可以使用 [`$lib/server`](server-only-modules) 别名来导入。SvelteKit 将防止您在客户端代码中导入这些内容。 * `params` 包含任何您的应用需要的 [参数匹配器](advanced-routing#Matching) * `路由` 包含了您应用程序的 [路由](routing)。您还可以在此处放置仅用于单个路由的其他组件。 * `app.html` 是您的页面模板 — 一个包含以下占位符的 HTML 文档: * `%sveltekit.head%` — `` 和 `

{data.title}

{@html data.content}
``` > \[!旧版\] `PageProps` 在 2.16.0 版本中添加。在早期版本中,您必须手动输入 `data` 属性,使用 `PageData` 代替,参见 \[类型\](#\\types)。 > > 在 Svelte 4 中,您将使用 `export let data` 代替。 ### +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`,`+page.js`还可以导出配置页面行为的值: * `export const prerender = true` 或 `false` 或 `'auto'` * `export const ssr = true` 或 `false` * `export const csr = true` 或 `false` 您可以在[页面选项](page-options)中找到更多关于这些信息。 ### +page.server.js 如果您的`load`函数只能在服务器上运行——例如,如果它需要从数据库获取数据或您需要访问像 API 密钥这样的私有[环境变量]($env-static-private)——那么您可以重命名`+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)进行序列化。请参阅[`load`](load)以获取 API 的完整详细信息。 与`+page.js`类似,`+page.server.js`可以导出[页面选项](page-options)——`预渲染`、`服务器端渲染`和`客户端渲染`。 一个 `+page.server.js` 文件也可以导出 *actions*。如果 `load` 允许您从服务器读取数据,`actions` 则允许您使用 `
` 元素将数据 *写入* 服务器。要了解如何使用它们,请参阅 [表单操作](form-actions) 部分。 ## +错误 如果加载过程中发生错误,SvelteKit 将渲染默认错误页面。您可以通过添加一个`+error.svelte`文件来根据每个路由自定义此错误页面: ```svelte

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

``` > \[!旧版\] `$app/state` 在 SvelteKit 2.12 中被添加。如果您使用的是更早的版本或使用 Svelte 4,请使用 `$app/stores` 代替。 SvelteKit 将“遍历树形结构”以寻找最近的错误边界——如果上面的文件不存在,它将尝试`src/routes/blog/+error.svelte`,然后是`src/routes/+error.svelte`,在渲染默认错误页面之前。如果*这*失败了(或者如果错误是从根`load`函数中抛出的,该函数位于根`+layout`之上,而`+error`位于其“上方”),SvelteKit 将退出并渲染一个静态的回退错误页面,您可以通过创建一个`src/error.html`文件来自定义它。 如果错误发生在 `load` 函数内部,在 `+layout(.server).js` 中,树中最接近的错误边界是一个位于布局 *上方* 的 `+error.svelte` 文件(而不是紧挨着它)。 如果找不到路由(404),将使用`src/routes/+error.svelte`(或默认错误页面,如果该文件不存在)。 > \[!注意\] `+error.svelte` 在 [`handle`](hooks#Server-hooks-handle) 或 [+server.js](#server) 请求处理器内部发生错误时 *不* 被使用。 您可以在[这里](errors)了解更多关于错误处理的信息。 ## +布局 截至目前,我们将页面视为完全独立的组件——在导航时,现有的`+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))。 我们可以创建一个仅适用于页面下方 `/settings` 的布局(同时继承顶级导航的根布局): ```svelte

Settings

{@render children()} ``` > \[!旧版\] `LayoutProps` 在 2.16.0 版本中添加。在早期版本中,您必须 [手动输入属性](#$types)。 您可以通过查看下一节中位于下面的`+layout.js`示例来了解`数据`是如何填充的。 默认情况下,每个布局都继承其上方的布局。有时这并不是你想要的 - 在这种情况下,[高级布局](advanced-routing#Advanced-layouts)可以帮助你。 ### +layout.js 就像 `+page.svelte` 从 `+page.js` 加载数据一样,你的 `+layout.svelte` 组件可以从 `+layout.js` 中的 [`load`](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) —— `预渲染`、`SSR` 和 `CSR` —— 它们将被用作子页面的默认值。 数据从布局的 `加载` 函数返回,也对其所有子页面可用: ```svelte ``` > \[注意\] 在页面间导航时,布局数据通常保持不变。SvelteKit 将在必要时智能地重新运行[`加载`](load)函数。 ### +layout.server.js 要在服务器上运行您布局的`load`函数,请将其移动到`+layout.server.js`,并将`LayoutLoad`类型更改为`LayoutServerLoad`。 与`+layout.js`类似,`+layout.server.js`可以导出[页面选项](page-options)——`预渲染`、`服务器端渲染`和`客户端渲染`。 ## +服务器 以及页面,您还可以使用`+server.js`文件(有时称为“API 路由”或“端点”)来定义路由,该文件让您完全控制响应。您的`+server.js`文件导出与 HTTP 动词(如`GET`、`POST`、`PATCH`、`PUT`、`DELETE`、`OPTIONS`和`HEAD`)对应的函数,这些函数接受一个[`RequestEvent`](@sveltejs-kit#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)); } ``` 第一个参数可以是[`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream),这使得可以流式传输大量数据或创建服务器发送事件(除非部署到像 AWS Lambda 这样的缓冲响应的平台)。 您可以使用来自 `@sveltejs/kit` 的 [`error`](@sveltejs-kit#error)、[`redirect`](@sveltejs-kit#redirect) 和 [`json`](@sveltejs-kit#json) 方法以提高便利性(但您不必这样做)。 如果抛出错误(无论是 `error(...)` 还是意外错误),响应将是一个错误或回退错误页面的 JSON 表示形式——可以通过 `src/error.html` 进行自定义——具体取决于 `Accept` 头部。在这种情况下,[`+error.svelte`](#error) 组件将 *不会* 被渲染。您可以在 [这里](errors) 了解更多关于错误处理的信息。 > \[!注意\] 当创建一个 `OPTIONS` 处理器时,请注意 Vite 将注入 `Access-Control-Allow-Origin` 和 `Access-Control-Allow-Methods` 头部信息——除非您添加它们,否则在生产环境中这些信息将不存在。 > \[!注意\] `+layout` 文件对 `+server.js` 文件没有影响。如果您想在每次请求之前运行一些逻辑,请将其添加到服务器的 [`handle`](hooks#Server-hooks-handle) 钩子中。 ### 接收数据 通过导出 `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); } ``` > \[!注意\] 通常,[表单操作](form-actions)是从浏览器向服务器提交数据的一种更好的方式。 > \[注意\] 如果导出 `GET` 处理器,则 `HEAD` 请求将返回 `content-length`,这是 `GET` 处理器响应体的长度。 ### 回退方法处理器 导出 `fallback` 处理器将匹配任何未处理的请求方法,包括像 `MOVE` 这样没有从 `+server.js` 中专门导出的方法。 ```js /// file: src/routes/api/add/+server.js import { json, text } from '@sveltejs/kit'; /** @type {import('./$types').RequestHandler} */ 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!`); } ``` > \[!注意\] 对于 `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`文件中导入类型。这是一个 SvelteKit 为您在隐藏目录中创建的文件,如果您使用 TypeScript(或带有 JSDoc 类型注解的 JavaScript)进行开发,它会在处理根文件时为您提供类型安全。 例如,将 `let { data } = $props()` 注释为 `PageProps`(或 `LayoutProps`,对于 `+layout.svelte` 文件)告诉 TypeScript,`data` 的类型是 `load` 返回的内容。 ```svelte ``` > \[注意\] 在 2.16.0 版本中添加的 `PageProps` 和 `LayoutProps` 类型,是输入 `data` 属性为 `PageData` 或 `LayoutData` 的快捷方式,以及其他属性,例如页面的 `form` 或布局的 `children`。在早期版本中,您必须手动输入这些属性。例如,对于一个页面: > > ```js > /// file: +page.svelte > /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ > let { data, form } = $props(); > ``` > > 或者,对于布局: > > ```js > /// file: +layout.svelte > /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ > let { data, children } = $props(); > ``` 依次注释 `load` 函数为 `PageLoad`、`PageServerLoad`、`LayoutLoad` 或 `LayoutServerLoad`(分别对应于 `+page.js`、`+page.server.js`、`+layout.js` 和 `+layout.server.js`),确保 `params` 和返回值类型正确。 如果您正在使用 VS Code 或支持语言服务器协议和 TypeScript 插件的任何 IDE,那么您可以完全省略这些类型!Svelte 的 IDE 工具将为您插入正确的类型,因此您将获得类型检查而无需自己编写。它还与我们的命令行工具 `svelte-check` 一起工作。 您可以在我们的[博客文章](/blog/zero-config-type-safety)中了解更多关于省略`$types`的信息。 ## 其他文件 任何位于路由目录内的其他文件都将被 SvelteKit 忽略。这意味着您可以将组件和需要它们的实用模块与路由一起放置。 如果组件和模块被多个路由需要,将它们放在[`$lib`]($lib)中是个好主意。 ## 进一步阅读 * [ 教程:路由](/tutorial/kit/pages) * [ 教程:API 路由](/tutorial/kit/get-handlers) * [ 文档:高级路由](advanced-routing) # Loading data 在渲染一个 [`+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}
``` > \[!旧版\]在 2.16.0 版本之前,页面和布局的属性必须单独输入: > > ```js > /// file: +page.svelte > /** @type {{ data: import('./$types').PageData }} */ > let { data } = $props(); > ``` > > 在 Svelte 4 中,您将使用 `export let data` 代替。 感谢生成的 `$types` 模块,我们获得了完全的类型安全。 一个在 `load` 文件中的 `+page.js` 函数在服务器和浏览器上都会运行(除非与 `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`,请参阅 [通用与服务器](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()}
``` > \[!旧版\] `LayoutProps` 在 2.16.0 版本中添加。在早期版本中,属性必须单独指定类型: > > ```js > /// file: +layout.svelte > /** @type {{ data: import('./$types').LayoutData, children: Snippet }} */ > let { data, children } = $props(); > ``` 数据从 `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}+++ ``` > \[注意\] 如果多个 `load` 函数返回具有相同键的数据,则最后一个“获胜”——一个布局 `load` 返回 `{ a: 1, b: 2 }` 和一个页面 `load` 返回 `{ b: 3, c: 4 }` 的结果将是 `{ a: 1, b: 3, c: 4 }`。 ## 页面数据 组件 `+page.svelte` 以及其上方的每个 `+layout.svelte` 组件,都可以访问自己的数据以及其父级的所有数据。 在某些情况下,我们可能需要相反的情况——父布局可能需要访问页面数据或子布局的数据。例如,根布局可能希望访问从`+page.js`或`+page.server.js`中的`load`函数返回的`title`属性。这可以通过`page.data`来实现: ```svelte {page.data.title} ``` 类型信息由 `page.data` 提供,由 `App.PageData` 提供。 > \[!旧版\] `$app/state` 在 SvelteKit 2.12 中被添加。如果您使用的是更早的版本或使用 Svelte 4,请使用 `$app/stores` 代替。它提供了一个具有相同接口的 `page` 存储,您可以订阅它,例如 `$page.data.title`。 ## 通用与服务器 我们已看到,有两种类型的 `load` 函数: * `+page.js` 和 `+layout.js` 文件导出 *通用*`加载` 函数,这些函数在服务器和浏览器上都能运行 * `+page.server.js` 和 `+layout.server.js` 文件导出 *server*`load` 函数,这些函数仅在服务器端运行 概念上,它们是同一件事,但有一些重要的差异需要注意。 ### 何时运行哪个加载函数? 服务器 `加载` 函数 *总是* 在服务器上运行。 默认情况下,通用 `加载` 函数在 SSR(服务器端渲染)期间用户首次访问您的页面时在服务器上运行。然后它们将在 hydration 期间再次运行,重用来自 [fetch 请求](#Making-fetch-requests) 的任何响应。所有后续的通用 `加载` 函数调用都发生在浏览器中。您可以通过 [页面选项](page-options) 定制行为。如果您禁用 [服务器端渲染](page-options#ssr),您将获得一个 SPA,并且通用 `加载` 函数 *始终* 在客户端运行。 如果路由同时包含通用和服务器 `加载` 函数,则服务器 `加载` 先运行。 A `加载`函数在运行时被调用,除非您[预渲染](page-options#prerender)页面——在这种情况下,它在构建时被调用。 ### 输入 两者都通用的和服务器 `load` 函数可以访问描述请求的属性(`params`、`route` 和 `url`)以及各种函数(`fetch`、`setHeaders`、`parent`、`depends` 和 `untrack`)。这些内容在以下章节中描述。 服务器 `load` 函数通过一个 `ServerLoadEvent` 被调用,该事件继承自 `clientAddress`、`cookies`、`locals`、`platform` 和 `request`,它们都来自 `RequestEvent`。 通用`load`函数使用`LoadEvent`调用,该事件具有`data`属性。如果您在`+page.js`和`+page.server.js`(或`+layout.js`和`+layout.server.js`)中都有`load`函数,则服务器`load`函数的返回值是通用`load`函数参数的`data`属性。 ### Input: Output: 一个通用的`load`函数可以返回一个包含任何值的对象,包括像自定义类和组件构造函数这样的东西。 服务器 `加载` 函数必须返回可以与 [反序列化](https://github.com/rich-harris/devalue) 一并序列化的数据——任何可以表示为 JSON 的内容以及类似 `BigInt`、`Date`、`Map`、`Set` 和 `RegExp` 的内容,或者重复/循环引用——以便通过网络传输。如果您的数据包含 [承诺](#Streaming-with-promises),则将流式传输到浏览器。如果您需要序列化/反序列化自定义类型,请使用 [传输钩子](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport)。 ### 何时使用哪个 服务器 `加载` 函数在您需要直接从数据库或文件系统访问数据或需要使用私有环境变量时很方便。 通用 `加载` 函数在您需要从外部 API `获取` 数据且不需要私有凭证时很有用,因为 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 一个包含如`origin`、`hostname`、`pathname`和`searchParams`(其中包含解析后的查询字符串作为[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)对象)等属性的[`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)实例。在`load`期间无法访问`url.hash`,因为在服务器上不可用。 > \[注意\] 在某些环境中,这是在服务器端渲染期间从请求头中派生出来的。如果您使用[adapter-node](adapter-node),例如,您可能需要配置适配器,以便 URL 正确。 ### 路由 包含当前路由目录名称,相对于 `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` 对象将如下所示: `{ "b": "x" c: y/z }` ## 制作获取请求 要从外部 API 或一个`+server.js`处理器获取数据,您可以使用提供的`fetch`函数,该函数的行为与[原生的`fetch`网络 API](https://developer.mozilla.org/en-US/docs/Web/API/fetch)相同,并增加了几个额外功能: * 它可以用来在服务器上发起认证请求,因为它继承了页面请求的`cookie`和`authorization`头部。 * 它可以对服务器进行相对请求(通常情况下,在服务器环境中使用 `fetch` 需要一个带有源头的 URL)。 * 内部请求(例如对 `+server.js` 路由的请求)在服务器上运行时直接发送到处理函数,无需 HTTP 调用的开销。 * 在服务器端渲染过程中,响应将被捕获并内联到渲染的 HTML 中,通过挂钩到`text`、`json`和`arrayBuffer`方法以及`Response`对象。请注意,除非明确通过 [`filterSerializedResponseHeaders`](hooks#Server-hooks-handle) 包含,否则头部信息将不会被序列化。 * 在加水过程中,响应将从 HTML 中读取,确保一致性并防止额外的网络请求 - 如果您在使用浏览器时在控制台收到警告,使用的是`fetch`而不是`load``fetch`,这就是原因。 ```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 服务器 `加载` 函数可以获取和设置 [`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) }; } ``` Cookies 仅当目标主机与 SvelteKit 应用程序或其更具体的子域名相同时,才会通过提供的 `fetch` 函数传递。 例如,如果 SvelteKit 正在为 my.domain.com 提供服务: * domain.com 不会接收 cookies * my.domain.com 将会接收 cookies * api.domain.com 不会接收 cookies * sub.my.domain.com 将会接收 cookies 其他 cookie 在设置`credentials: 'include'`时不会被传递,因为 SvelteKit 不知道哪个 cookie 属于哪个域名(浏览器不会传递这些信息),因此转发任何这些 cookie 都不安全。使用[handleFetch 钩子](hooks#Server-hooks-handleFetch)来解决这个问题。 ## 头信息 服务器和通用 `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)` 代替。 ## 使用父数据 偶尔,对于 `加载` 函数来说,访问父 `加载` 函数中的数据是有用的,这可以通过 `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}

``` > \[!注意\] 注意到 `load` 函数在 `+page.js` 中接收来自布局 `load` 函数的合并数据,而不仅仅是直接父级。 在`+page.server.js`和`+layout.server.js`内部,`parent`从父`+layout.server.js`文件中返回数据。 在`+page.js`或`+layout.js`中,它将返回父`+layout.js`文件中的数据。然而,缺失的`+layout.js`被视为一个`({ data }) => data`函数,这意味着它也将返回父`+layout.server.js`文件中的数据,这些数据不会被`+layout.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 } }; } ``` ## 错误 如果在 `加载` 过程中抛出错误,则将渲染最近的 [`+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 内部错误。 > \[!注意\] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you)您必须 `手动抛出` 错误 ## 重定向 要重定向用户,请使用来自 `@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'); } } ``` > \[注意\] 不要在 `try {...}` 块内使用 `redirect()`,因为重定向将立即触发 catch 语句。 在浏览器中,您还可以使用 [`$app.navigation`]($app-navigation) 中的 [`goto`]($app-navigation#goto) 从 `load` 函数外进行程序化导航。 > \[!注意\] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you)您必须自己 `抛出` `重定向` ## 流式传输与承诺 当使用服务器 `加载` 时,一旦解决,承诺将被流式传输到浏览器。如果您有缓慢的非关键数据,这很有用,因为您可以在所有数据可用之前开始渲染页面: ```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} ``` 在流式传输数据时,请小心正确处理承诺拒绝。更具体地说,如果延迟加载的承诺在渲染开始之前失败(此时会被捕获)并且没有以某种方式处理错误,服务器可能会因为“未处理的承诺拒绝”错误而崩溃。当在`load`函数中直接使用 SvelteKit 的`fetch`时,SvelteKit 会为你处理这种情况。对于其他承诺,只需将一个 noop-`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() }; } ``` > \[注意\] 在不支持流式传输的平台,例如 AWS Lambda 或 Firebase 上,响应将被缓冲。这意味着页面将仅在所有承诺解决后才会渲染。如果您正在使用代理(例如 NGINX),请确保它不会缓冲代理服务器的响应。 > \[注意\] 仅当启用 JavaScript 时,流式数据才会工作。如果页面是服务器渲染的,应避免从通用的`load`函数返回承诺,因为这些不是*流式*的——相反,当函数在浏览器中重新运行时,承诺将被重新创建。 > \[!注意\] 响应一旦开始流式传输,其头部和状态码就不能更改,因此您不能在流式承诺中 `设置头部` 或抛出重定向。 > \[!注意\] [在 SvelteKit 1.x 中](migrating-to-sveltekit-2#Top-level-promises-are-no-longer-awaited)顶级承诺自动等待,只有嵌套承诺被流式传输。 ## 并行加载 当渲染(或导航到)页面时,SvelteKit 会并发运行所有`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` 函数重新运行时重新运行。 依赖跟踪在 *加载* 函数返回后不适用 —— 例如,在嵌套的 [承诺](#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!' }; } } ``` ### 手动失效 您也可以使用`load`函数重新运行适用于当前页面的功能,通过[`invalidate(url)`]($app-navigation#invalidate),这将重新运行所有依赖于`url`的`load`函数,以及[`invalidateAll()`]($app-navigation#invalidateAll),这将重新运行每个`load`函数。服务器负载函数永远不会自动依赖于获取的`url`,以避免将秘密泄露给客户端。 一个 `load` 函数在调用 `url` 时依赖于它,如果它调用了 `fetch(url)` 或 `depends(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` 函数: * 它引用了`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()` 并正在重新运行,而父函数是一个服务器负载函数 * 它通过[`fetch`](#Making-fetch-requests)(仅限通用加载)或[`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) 调用或 [`redirect`](@sveltejs-kit#redirect) 重定向进行更改。 请注意,重新运行一个 `load` 函数将更新对应于 `+layout.svelte` 或 `+page.svelte` 的 `data` 属性;它不会导致组件被重新创建。因此,内部状态得以保留。如果这不是您想要的结果,您可以在 [`afterNavigate`]($app-navigation#afterNavigate) 回调中重置您需要重置的任何内容,或者将您的组件包裹在一个 [`{#key ...}`](../svelte/key) 块中。 ## 认证的影响 一些数据加载功能对身份验证检查具有重要意义: * 布局 `加载` 函数不在每个请求上运行,例如在客户端导航子路由之间时。 [(加载函数何时重新运行?)](load#Rerunning-load-functions-When-do-load-functions-rerun) * 布局和页面 `加载` 函数并发运行,除非调用 `await parent()`。如果布局 `加载` 抛出异常,则页面 `加载` 函数运行,但客户端将不会接收到返回的数据。 有几种可能的策略来确保在受保护的代码之前进行身份验证检查。 为防止数据瀑布并保留布局 `load` 缓存: * 使用[钩子](hooks)在所有`加载`函数运行之前保护多个路由 * 直接在 `+page.server.js``load` 函数中使用 auth guards 进行路由特定保护 在 `+layout.server.js` 中添加身份验证守卫要求所有子页面在受保护代码之前调用 `await parent()`。除非每个子页面都依赖于从 `await parent()` 返回的数据,否则其他选项将具有更高的性能。 ## 使用 `getRequestEvent` 当运行服务器 `load` 函数时,作为函数参数传递的 `event` 对象也可以通过 [`getRequestEvent`]($app-server#getRequestEvent) 获取。这允许共享逻辑(如身份验证守卫)在不需要传递的情况下访问当前请求的信息。 例如,您可能有一个需要用户登录的功能,如果未登录,则重定向到 `/login` ```js /// file: src/lib/server/auth.js // @filename: ambient.d.ts interface User { name: string; } declare namespace App { interface Locals { user?: User; } } // @filename: index.ts // ---cut--- import { redirect } from '@sveltejs/kit'; import { getRequestEvent } from '$app/server'; export function requireLogin() { const { locals, url } = getRequestEvent(); // assume `locals.user` is populated in `handle` if (!locals.user) { const redirectTo = url.pathname + url.search; const params = new URLSearchParams({ redirectTo }); redirect(307, `/login?${params}`); } return locals.user; } ``` 现在,您可以在任何`load`函数(或例如[表单操作)中调用`requireLogin`,以确保用户已登录:](form-actions) ```js /// file: +page.server.js // @filename: ambient.d.ts declare module '$lib/server/auth' { interface User { name: string; } export function requireLogin(): User; } // @filename: index.ts // ---cut--- import { requireLogin } from '$lib/server/auth'; export function load() { const user = requireLogin(); // `user` is guaranteed to be a user object here, because otherwise // `requireLogin` would throw a redirect and we wouldn't get here return { message: `hello ${user.name}!` }; } ``` ## 进一步阅读 * [ 教程:加载数据](/tutorial/kit/page-data) * [ 教程:错误和重定向](/tutorial/kit/error-basics) * [ 教程:高级加载](/tutorial/kit/await-parent) # Form actions 一个 `+page.server.js` 文件可以导出 *actions*,这些操作允许您使用 `POST` 数据通过 `` 元素发送到服务器。 当使用``时,客户端 JavaScript 是可选的,但您可以使用 JavaScript 轻松地*渐进式增强*您的表单交互,以提供最佳的用户体验。 ## 默认操作 在最简单的情况下,一个页面声明了一个 `默认`动作: ```js /// file: src/routes/login/+page.server.js /** @satisfies {import('./$types').Actions} */ export const actions = { default: async (event) => { // TODO log the user in } }; ``` 要从此 `/login` 页面调用此操作,只需添加一个 `` — 不需要 JavaScript: ```svelte ``` 如果有人点击按钮,浏览器将通过`POST`请求将表单数据发送到运行默认操作的服务器。 > \[!注意\] 操作始终使用 `POST` 请求,因为 `GET` 请求不应产生副作用。 我们也可以从其他页面调用操作(例如,如果根布局中的导航中有登录小部件)通过添加`操作`属性,指向页面: ```html /// file: src/routes/+layout.svelte
``` ## 命名动作 而不是一个 `默认` 动作,一个页面可以有它需要的任意多个命名动作: ```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 }+++ }; ``` 调用命名操作时,添加一个以`/`字符为前缀的查询参数: ```svelte
``` ```svelte ``` 除了 `action` 属性外,我们还可以在按钮上使用 `formaction` 属性将相同的表单数据 `POST` 到与父 `` 不同的操作: ```svelte /// file: src/routes/login/+page.svelte ++++++
``` > \[注意\] 我们不能在命名操作旁边有默认操作,因为如果您在没有重定向的情况下向命名操作 POST,查询参数将保留在 URL 中,这意味着下一个默认 POST 将通过之前的命名操作进行。 ## 动作解剖 每个操作都接收一个 `RequestEvent` 对象,允许您使用 `request.formData()` 读取数据。在处理请求(例如,通过设置 cookie 登录用户)之后,操作可以响应将通过相应页面的 `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} ``` > \[!旧版\] `PageProps` 在 2.16.0 版本中添加。在早期版本中,您必须单独输入 `data` 和 `form` 属性: > > ```js > /// file: +page.svelte > /** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */ > let { data, form } = $props(); > ``` > > 在 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 } }; ``` > \[!注意\] 注意,作为预防措施,我们只将电子邮件返回到页面,而不是密码。 ```svelte /// file: src/routes/login/+page.svelte
+++ {#if form?.missing}

The email field is required

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

Invalid credentials!

{/if}+++
``` 返回的数据必须可序列化为 JSON。除此之外,结构完全由您决定。例如,如果您在页面上有多个表单,您可以使用一个`form`的`form`数据,通过一个`id`属性或类似的方式来区分它们。 ### 重定向 重定向(和错误)与在[`加载`](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 } }; ``` ## 加载数据 在动作执行后,页面将重新渲染(除非发生重定向或意外错误),动作的返回值将以 `form` 属性的形式提供给页面。这意味着您的页面 `load` 函数将在动作完成后运行。 请注意,`handle` 在调用动作之前运行,并在 `load` 函数之前不会重新运行。这意味着,例如,如果您使用 `handle` 根据 cookie 填充 `event.locals`,则在动作中设置或删除 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; } }; ``` ## 渐进增强 在前面的章节中,我们构建了一个 `/login` 动作,该动作 [无需客户端 JavaScript](https://kryogenix.org/code/browser/everyonehasjs.html) 即可运行——没有看到任何 `fetch`。这很好,但当 JavaScript *可用* 时,我们可以逐步增强我们的表单交互,以提供更好的用户体验。 ### 使用:增强 最简单的方法逐步增强表单是添加 `use:enhance` 操作: ```svelte /// file: src/routes/login/+page.svelte
``` > \[注意\] `use:enhance` 只能与具有 `method="POST"` 的表单以及指向定义在 `+page.server.js` 文件中的操作的表单一起使用。它不能与 `method="GET"` 一起使用,这是未指定方法的表单的默认方法。在未指定 `method="POST"` 的表单上使用 `use:enhance` 或向 `+server.js` 端点提交将导致错误。 > \[!注意\] 是的,有点令人困惑,`增强`操作和``都被称为'操作'。这些文档充满了操作。抱歉。 无参数时,使用`use:enhance`将模拟浏览器原生行为,只是没有整个页面的刷新。它将: * 更新 `表单` 属性、`page.form` 和 `page.status` 在成功或无效响应时,但仅当操作是在您提交的同一页面上时。例如,如果您的表单看起来像 `` ,则 `表单` 属性和 `page.form` 状态将 *不会* 更新。这是因为在本机表单提交的情况下,您将被重定向到操作所在的页面。如果您想无论哪种情况都更新它们,请使用 [`applyAction`](#Progressive-enhancement-Customising-use:enhance)。 * 重置 `` 元素 * 使用`invalidateAll`在成功响应中使所有数据无效 * 调用重定向响应中的 `goto` * 渲染最近的 `+错误` 边界,如果发生错误 * [重置焦点](accessibility#Focus-management)到适当的元素 ### 自定义使用:增强 要自定义行为,您可以在表单提交前提供一个立即运行的 `SubmitFunction`,并且(可选地)返回一个在 `ActionResult` 中运行的回调。 ```svelte { // `formElement` is this `` element // `formData` is its `FormData` object that's about to be submitted // `action` is the URL to which the form is posted // calling `cancel()` will prevent the submission // `submitter` is the `HTMLElement` that caused the form to be submitted return async ({ result, update }) => { // `result` is an `ActionResult` object // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set }; }} > ``` 您可以使用这些函数来显示和隐藏加载 UI 等。 如果您返回一个回调,您将覆盖默认的提交后行为。要恢复,请调用`update`,它接受`invalidateAll`和`reset`参数,或者在对结果使用`applyAction`: ```svelte /// file: src/routes/login/+page.svelte { return async ({ result }) => { // `result` is an `ActionResult` object +++ if (result.type === 'redirect') { goto(result.location); } else { await applyAction(result); }+++ }; }} > ``` 行为取决于 `applyAction(result)` 中的 `result.type`: * `成功`,`失败` — 将`page.status`设置为`result.status`,并更新`form`和`page.form`为`result.data`(无论您从哪里提交,与从`enhance`的`update`相比) * `重定向` — 调用 `goto(result.location, { invalidateAll: true })` * `错误` — 以 `+错误` 边界渲染 `result.error` 在所有情况下,[焦点将被重置](accessibility#Focus-management)。 ### 自定义事件监听器 我们也可以自行实现渐进增强,无需使用`use:enhance`,只需在``上添加一个正常的事件监听器: ```svelte
``` 请注意,在进一步使用相应方法从 `$app/forms` 处理之前,您需要先 `反序列化` 响应。仅使用 `JSON.parse()` 不够,因为表单操作 - 如 `load` 函数 - 也支持返回 `Date` 或 `BigInt` 对象。 如果您旁边有一个`+server.js`和`+page.server.js`,默认情况下,`fetch`请求将被路由到那里。要向`+page.server.js`中的操作`POST`,请使用自定义`x-sveltekit-action`头: ```js const response = await fetch(this.action, { method: 'POST', body: data, +++ headers: { 'x-sveltekit-action': 'true' }+++ }); ``` ## 替代方案 表单操作是向服务器发送数据的首选方式,因为它们可以被逐步增强,但您也可以使用 [`+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 我们已看到,要调用表单操作,您必须使用`method="POST"`。 一些表单不需要向服务器发送`POST`数据——例如搜索输入。对于这些,您可以使用`method="GET"`(或者,等价地,根本不设置`method`),SvelteKit 会将它们视为`
`元素,使用客户端路由而不是完整页面导航: ```html
``` 提交此表单将导航到 `/search?q=...` 并调用您的加载函数,但不会调用操作。与 `
` 元素一样,您可以在 `
` 上设置 [`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) # Page options 默认情况下,SvelteKit 会首先在服务器上渲染(或[预渲染](glossary#Prerendering))任何组件,并将其作为 HTML 发送到客户端。然后,它会在浏览器中再次渲染该组件,使其在称为[*激活*](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; ``` 或者,您可以在根目录的 `export const prerender = true` 中设置,或者在 `+layout.js` 或 `+layout.server.js` 中设置,并将除了明确标记为 *不可* 渲染的页面之外的所有内容进行预渲染: ```js /// file: +page.js/+page.server.js/+server.js export const prerender = false; ``` 路由中带有 `prerender = true` 的将被排除在用于动态 SSR 的清单之外,使您的服务器(或无服务器/边缘函数)更小。在某些情况下,您可能希望预渲染一个路由但同时也将其包含在清单中(例如,使用 `/blog/[slug]` 这样的路由,您希望预渲染最新的/最受欢迎的内容但服务器端渲染长尾)—— 对于这些情况,还有一个第三种选项,'auto': ```js /// file: +page.js/+page.server.js/+server.js export const prerender = 'auto'; ``` > \[注意\] 如果您的整个应用程序适合预渲染,您可以使用 [`adapter-static`](adapter-static),这将输出适用于任何静态 Web 服务器的文件。 预渲染器将从您的应用根目录开始,为找到的任何可预渲染页面或`+server.js`路由生成文件。每个页面都会扫描指向其他页面(这些页面是预渲染的候选者)的``元素——正因为如此,您通常不需要指定哪些页面应该被访问。如果您*确实*需要指定预渲染器应该访问哪些页面,您可以通过`config.kit.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` 。 ### 何时不进行预渲染 基本规则是这样的:为了使页面可预渲染,任何两个直接访问该页面的用户必须从服务器获得相同的内容。 > \[注意\] 并非所有页面都适合预渲染。任何预渲染的内容都将被所有用户看到。当然,您可以在预渲染页面的`onMount`中获取个性化数据,但这可能会导致用户体验较差,因为这将涉及空白初始内容或加载指示器。 请注意,您仍然可以预渲染基于页面参数加载数据的页面,例如 `src/routes/blog/[slug]/+page.svelte` 路由。 访问 [`url.searchParams`](load#Using-URL-data-url) 在预渲染期间是禁止的。如果您需要使用它,请确保您只在使用浏览器时这样做(例如在 `onMount`)。 页面中包含 [操作](form-actions) 的页面无法预渲染,因为服务器必须能够处理 `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`。 ### 故障排除 如果您遇到类似“以下路由被标记为可预渲染,但未进行预渲染”的错误,那是因为相关路由(或页面是父布局的情况)中存在 `export const prerender = true`,但页面未被预渲染爬虫访问,因此未进行预渲染。 由于这些路由无法动态服务器端渲染,当人们尝试访问相关路由时将导致错误。有几种方法可以修复它: * 确保 SvelteKit 可以通过以下链接找到路由:从[`config.kit.prerender.entries`](configuration#prerender)或[`entries`](#entries)页面选项。如果动态路由(即带有`[参数]`的页面)在其他入口点未找到,请将链接添加到此选项中;否则,由于 SvelteKit 不知道参数应该有什么值,它们将不会被预渲染。未标记为可预渲染的页面将被忽略,并且它们到其他页面的链接将不会被爬取,即使其中一些可能是可预渲染的。 * 确保 SvelteKit 可以通过从您已启用服务器端渲染的另一个预渲染页面中发现链接来找到该路由。 * 修改 `export const prerender = true` 为 `export const prerender = 'auto'` 。带有 `'auto'` 的路由可以动态服务器渲染 ## 条目 SvelteKit 将自动发现页面进行预渲染,从 *入口点* 开始并遍历它们。默认情况下,所有非动态路由都被视为入口点——例如,如果您有这些路由... ```bash / # non-dynamic /blog # non-dynamic /blog/[slug] # dynamic, because of `[slug]` ``` ...SvelteKit 将预渲染 `/` 和 `/blog`,并在过程中发现类似 `` 的链接,这些链接为它提供了新的预渲染页面。 大多数情况下,这已经足够了。在某些情况下,像`/blog/hello-world`这样的页面链接可能不存在(或者在预渲染页面上可能不存在),在这种情况下,我们需要告诉 SvelteKit 它们的存在。 这可以通过[`config.kit.prerender.entries`](configuration#prerender)来完成,或者通过从一个属于动态路由的`entries`函数、`+page.js`、`+page.server.js`或`+server.js`中导出,来实现。 ```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; // If both `ssr` and `csr` are `false`, nothing will be rendered! ``` 如果您将`export const ssr = false`添加到您的根`+layout.js`中,您的整个应用将仅在客户端渲染——这实际上意味着您将应用转换为单页应用(SPA)。 > \[注意\] 如果您的所有页面选项都是布尔值或字符串字面量,SvelteKit 将静态评估它们。如果不是,它将在服务器上导入您的 `+page.js` 或 `+layout.js` 文件(在构建时间和如果您的应用程序不是完全静态的运行时),以便评估选项。在后一种情况下,当模块加载时,不应运行浏览器专用代码。实际上,这意味着您应该在您的 `+page.svelte` 或 `+layout.svelte` 文件中导入浏览器专用代码。 ## csr 通常,SvelteKit [将](glossary#Hydration)你的服务器端渲染的 HTML 转换为交互式的客户端渲染(CSR)页面。有些页面根本不需要 JavaScript——许多博客文章和“关于”页面都属于这一类。在这些情况下,你可以禁用 CSR: ```js /// file: +page.js export const csr = false; // If both `csr` and `ssr` are `false`, nothing will be rendered! ``` 禁用 CSR 不会向客户端发送任何 JavaScript。这意味着: * 网页应仅使用 HTML 和 CSS 工作。 * 所有 Svelte 组件中的 ` ``` ```svelte

Welcome {user().name}

``` > \[!注意\] 我们将一个函数传递给`setContext`以保持边界之间的响应性。了解更多信息[这里](/docs/svelte/$state#Passing-state-into-functions) > \[!旧版\] 您还使用来自 `svelte/store` 的存储库,但使用 Svelte 5 时,建议使用通用响应性。 更新在页面通过 SSR 渲染过程中更深层次的页面或组件的基于上下文的状态值时,不会影响父组件中的值,因为父组件在状态值更新之前已经渲染完成。相比之下,在客户端(当启用 CSR 时,这是默认设置)值将被传播,并且层次结构中更高的组件、页面和布局将响应新值。因此,为了避免在数据同步过程中状态更新时值“闪烁”,通常建议将状态向下传递到组件,而不是向上传递。 如果您不使用 SSR(并且可以保证未来不会需要使用 SSR)的话,那么您可以在共享模块中安全地保留状态,而不需要使用上下文 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`也不会重新计算。 相反,我们需要使值[*reactive*](/tutorial/svelte/state): ```svelte /// file: src/routes/blog/[slug]/+page.svelte ``` > \[!注意\] 如果您在 `onMount` 和 `onDestroy` 中的代码在导航后需要再次运行,可以使用 [afterNavigate]($app-navigation#afterNavigate) 和 [beforeNavigate]($app-navigation#beforeNavigate) 分别。 组件的重用意味着侧边栏滚动状态等类似功能得以保留,并且可以轻松地在不同的值之间进行动画转换。在你确实需要在导航时完全销毁并重新挂载组件的情况下,可以使用此模式: ```svelte {#key page.url.pathname} {/key} ``` ## 存储状态在 URL 中 如果您有需要在重新加载后仍然存在的状态,或者影响 SSR 的状态,例如表格上的过滤器或排序规则,URL 搜索参数(如`?sort=price&order=ascending`)是放置它们的好地方。您可以将它们放在`
`或``属性中,或者通过`goto('?key=value')`程序化设置。它们可以在`load`函数内部通过`url`参数访问,也可以在组件内部通过`page.url.searchParams`访问。 ## 存储临时状态到快照中 某些 UI 状态,例如“手风琴是否打开?”,是可丢弃的——如果用户离开或刷新页面,状态丢失无关紧要。在某些情况下,当用户导航到不同的页面并返回时,您可能希望数据持续存在,但将状态存储在 URL 或数据库中会过于冗余。为此,SvelteKit 提供了[快照](snapshots),允许您将组件状态与历史记录条目关联。 # Building your app 构建 SvelteKit 应用分为两个阶段,这两个阶段都在运行`vite build`(通常通过`npm run build`)时发生。 首先,Vite 为您的服务器代码、浏览器代码以及您的服务工作者(如果有)创建一个优化的生产构建。如果适用,此阶段将执行预渲染。 [预渲染](page-options#prerender) 其次,一个*适配器*将这个生产构建调整以适应您的目标环境——有关此内容的更多内容请参阅以下页面。 ## 在构建过程中 SvelteKit 将在构建过程中加载您的 `+page/layout(.server).js` 文件(以及它们导入的所有文件)进行分析。任何在此阶段不应执行 *不* 应执行的代码必须检查从 [`$app/environment`]($app-environment) `false` `building`: ```js +++import { building } from '$app/environment';+++ import { setupMyDatabase } from '$lib/server/database'; +++if (!building) {+++ setupMyDatabase(); +++}+++ export function load() { // ... } ``` ## 预览您的应用 构建完成后,您可以使用`vit 预览`(通过`npm run preview`)在本地查看您的生产构建。请注意,这将使用 Node 运行应用程序,因此这不是您部署的应用程序的完美复制——针对适配器的调整,如[`平台`对象](adapters#Platform-specific-context),不适用于预览。 # Adapters 在您部署 SvelteKit 应用程序之前,您需要将其针对部署目标进行*适配*。适配器是小型插件,它们以构建的应用程序作为输入,并生成用于部署的输出。 官方适配器适用于多种平台——这些适配器在以下页面上有文档说明: * [`@sveltejs/adapter-cloudflare`](adapter-cloudflare) 用于 Cloudflare Workers 和 Cloudflare Pages * [`@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({ // adapter options go here }) } }; export default config; ``` ## 平台特定上下文 某些适配器可能可以访问有关请求的附加信息。例如,Cloudflare Workers 可以访问一个包含 KV 命名空间等的`env`对象。这可以作为`RequestEvent`在[钩子](hooks)和[服务器路由](routing#server)中使用的`platform`属性传递——请查阅每个适配器的文档以获取更多信息。 # Zero-config deployments 当你使用`npx sv create`创建新的 SvelteKit 项目时,它默认安装了`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/) * [`سلت-适配器-azure-swa`](https://github.com/geoffrich/svelte-adapter-azure-swa) 用于 [Azure 静态 Web 应用](https://docs.microsoft.com/en-us/azure/static-web-apps/) * [`基于 SST 的 Svelte 套件`](https://github.com/sst/v2/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`中,因为这将适配器添加到您的锁文件中,并略微提高 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)并提交一个 pull request 来为额外的适配器添加零配置支持。 # Node servers 要生成一个独立的 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`来生成生产依赖(如果您的应用程序没有依赖项,可以跳过此步骤)。然后,您可以使用以下命令启动您的应用程序: `node 编译` 开发依赖将被打包到您的应用中,使用[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`... `npm 安装 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 ``` ### `端口`,`主机`和`SOCKET_PATH` 默认情况下,服务器将在`0.0.0.0`上使用端口 3000 接受连接。这些可以通过`PORT`和`HOST`环境变量进行自定义: ```bash HOST=127.0.0.1 PORT=4000 node build ``` 另外,服务器可以被配置为在指定的套接字路径上接受连接。当使用 `SOCKET_PATH` 环境变量执行此操作时,将忽略 `HOST` 和 `PORT` 环境变量。 ```bash SOCKET_PATH=/tmp/socket node build ``` ### `ORIGIN`,`PROTOCOL_HEADER`,`HOST_HEADER`和`PORT_HEADER` HTTP 不提供一种可靠的方法让 SvelteKit 知道当前正在请求的 URL。告诉 SvelteKit 应用正在被服务的最简单方法是将 `ORIGIN` 环境变量设置: ```bash ORIGIN=https://my.site node build # or e.g. for local previewing and testing ORIGIN=http://localhost:3000 node build ``` 使用此方法,对`/stuff`路径名的请求将正确解析为`https://my.site/stuff`。或者,您可以指定头信息,告知 SvelteKit 请求协议和主机,从而构建原始 URL: ```bash PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host node build ``` > \[注意\] [`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`。 如果 `适配器节点`无法正确确定您的部署的 URL,您在使用[表单操作](form-actions)时可能会遇到此错误: > \[注意\] 禁止跨站 POST 表单提交 ### `地址头` 和 `xff 深度` The [`RequestEvent`](@sveltejs-kit#RequestEvent) 对象传递给钩子和端点时,包括一个 `event.getClientAddress()` 函数,该函数返回客户端的 IP 地址。默认情况下,这是连接的 `remoteAddress`。如果您的服务器位于一个或多个代理(如负载均衡器)后面,则此值将包含最内层的代理的 IP 地址而不是客户端的,因此我们需要指定一个 `ADDRESS_HEADER` 来读取地址: ```bash ADDRESS_HEADER=True-Client-IP node build ``` > \[!注意\] 标题很容易被伪造。例如,与 `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`。 > \[注意\] 如果您需要读取最左侧的地址(并且不关心欺骗)——例如,提供地理位置服务,在这种情况下,IP 地址的真实性比可信性更重要,您可以通过检查您的应用程序中的`x-forwarded-for`头来实现这一点。 ### `BODY_SIZE_LIMIT` 最大可接受请求体大小(以字节为单位,包括流式传输时),也可以使用单位后缀指定千字节(`K`)、兆字节(`M`)或千兆字节(`G`)。例如,`512K` 或 `1M`。默认值为 512kb。您可以通过将值设置为`Infinity`(在适配器旧版本中为 0)来禁用此选项,并在需要更高级功能时在`handle`中实现自定义检查。 ### `SHUTDOWN_TIMEOUT` 等待在接收到 `SIGTERM` 或 `SIGINT` 信号后强制关闭任何剩余连接的秒数。默认为 `30`。内部适配器调用 [`closeAllConnections`](https://nodejs.org/api/http.html#servercloseallconnections)。有关更多详细信息,请参阅 [优雅关闭](#Graceful-shutdown)。 ### `空闲超时` 当使用 systemd 套接字激活时,`IDLE_TIMEOUT`指定了在收到无请求后应用自动休眠的秒数。如果没有设置,应用将连续运行。请参阅[套接字激活](#Socket-activation)获取更多详细信息。 ## 选项 适配器可以配置为各种选项: ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-node'; export default { kit: { adapter: adapter({ // default options are shown out: 'build', precompress: true, envPrefix: '' }) } }; ``` ### 出 服务器构建目录。默认为 `build` — 即 `node build` 将在创建后本地启动服务器。 ### 预压缩 启用使用 gzip 和 brotli 对资源和预渲染页面进行预压缩。默认为`true`。 ### envPrefix 如果您需要更改用于配置部署的环境变量名称(例如,为了避免与您无法控制的环境变量冲突),您可以指定一个前缀: ```js envPrefix: 'MY_CUSTOM_'; ``` ```bash MY_CUSTOM_HOST=127.0.0.1 \ MY_CUSTOM_PORT=4000 \ MY_CUSTOM_ORIGIN=https://my.site \ node build ``` ## 优雅关机 默认情况下,当接收到 `adapter-node` 或 `SIGTERM` 或 `SIGINT` 信号时,它会优雅地关闭 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)) > \[!注意\] 如果您想自定义此行为,可以使用[自定义服务器](#Custom-server)。 您可以在 HTTP 服务器关闭所有连接后监听 `sveltekit:shutdown` 事件。与 Node 的 `exit` 事件不同,`sveltekit:shutdown` 事件支持异步操作,并且当所有连接关闭时始终会触发,即使服务器有悬挂的工作,如打开的数据库连接。 ```js // @errors: 2304 process.on('sveltekit:shutdown', async (reason) => { await jobs.stop(); await db.close(); }); ``` 参数 `reason` 有以下几种值: * `SIGINT` - 关闭操作由 `SIGINT` 信号触发 * `SIGTERM` - 关闭操作由 `SIGTERM` 信号触发 * `空闲` - 关闭由 [`空闲超时`](#Environment-variables-IDLE_TIMEOUT) 触发 ## 套接字激活 大多数 Linux 操作系统今天使用名为 systemd 的现代进程管理器来启动服务器并运行和管理服务。您可以将服务器配置为分配套接字并在需要时启动和扩展您的应用程序。这被称为[套接字激活](http://0pointer.de/blog/projects/socket-activated-containers.html)。在这种情况下,操作系统将传递两个环境变量到您的应用程序中——`LISTEN_PID`和`LISTEN_FDS`。然后适配器将在文件描述符 3 上监听,该描述符指向您必须创建的系统 d 套接字单元。 > \[注意\] 您仍然可以使用 [`envPrefix`](#Options-envPrefix) 与 systemd 网络套接字激活一起使用。 `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. 创建一个配套的[套接字单元](https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html)。适配器仅接受一个套接字。 ```ini /// file: /etc/systemd/system/myapp.socket [Socket] ListenStream=3000 [Install] WantedBy=sockets.target ``` 3. 确保 systemd 已识别这两个单元,通过运行`sudo systemctl daemon-reload`。然后,在启动时启用套接字并立即使用 `sudo systemctl enable --now myapp.socket` 启动它。一旦对`localhost:3000`发起第一个请求,应用程序将自动启动。 ## 自定义服务器 适配器在您的构建目录中创建两个文件——`index.js`和`handler.js`。运行`index.js`——例如`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(); // add a route that lives separately from the SvelteKit app app.get('/healthcheck', (req, res) => { res.end('ok'); }); // let SvelteKit handle everything else, including serving prerendered pages and static assets app.use(handler); app.listen(3000, () => { console.log('listening on port 3000'); }); ``` # Static site generation 要使用 SvelteKit 作为静态网站生成器(SSG),请使用 [`adapter-static`](https://github.com/sveltejs/kit/tree/main/packages/adapter-static)。 这将为您的整个网站预渲染成一个静态文件的集合。如果您只想预渲染一些页面,而动态服务器渲染其他页面,您需要使用不同的适配器,并配合[以下`预渲染`选项](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({ // default options are shown. On some platforms // these options are set automatically — see below pages: 'build', assets: 'build', fallback: undefined, precompress: false, strict: true }) } }; ``` ...并在您的根布局中添加[`prerender`](page-options#prerender)选项: ```js /// file: src/routes/+layout.js // This can be false if you're using a fallback (i.e. SPA mode) export const prerender = true; ``` > \[!注意\] 您必须确保 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(---{...}---) } }; ``` ## 选项 ### 页 目录用于写入预渲染页面的位置。默认为 `build`。 ### 资产 目录用于写入静态资源(`static`的内容,以及由 SvelteKit 生成的客户端 JS 和 CSS)。通常,这应该与`pages`相同,并且默认为`pages`的值,但在罕见情况下,您可能需要将页面和资源输出到不同的位置。 ### 回退 指定一个用于 [SPA 模式](single-page-apps) 的回退页面,例如 `index.html` 或 `200.html` 或 `404.html`。 ### 预压缩 如果 `true`,则使用 brotli 和 gzip 预压缩文件。这将生成`.br`和`.gz`文件。 ### 严格 默认情况下,`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 # If you're using pnpm, add this step then change the commands and cache key below to use `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: # this should match the `pages` option in your adapter-static options 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 干扰。 # Single-page apps 您可以使用任何适配器将任何 SvelteKit 应用转换为完全客户端渲染的单页应用程序(SPA),方法是在根布局中禁用 SSR: ```js /// file: src/routes/+layout.js export const ssr = false; ``` > \[注意\] 在大多数情况下,这不被推荐:它损害 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' // may differ from host to host }) } }; ``` The `fallback` 页面是由 SvelteKit 从您的页面模板(例如 `app.html`)创建的 HTML 页面,用于加载您的应用程序并导航到正确的路由。例如 [Surge](https://surge.sh/help/adding-a-200-page-for-client-side-routing),一个静态网站托管服务,允许您添加一个 `200.html` 文件来处理与静态资源或预渲染页面不对应的任何请求。 在某些主机上可能是 `index.html` 或其他完全不同的内容——请查阅您的平台文档。 > \[注意\] 注意,回退页面始终包含绝对资产路径(即以`/`开头,而不是`.`),无论[`paths.relative`](configuration#paths)的值如何,因为它用于响应对任意路径的请求。 ## Apache 要运行 SPA 在[Apache](https://httpd.apache.org/)上,您应该添加一个`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 要部署到 [Cloudflare Workers](https://workers.cloudflare.com/) 或 [Cloudflare Pages](https://pages.cloudflare.com/),请使用 [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare)。 此适配器将在您使用[`adapter-auto`](adapter-auto)时默认安装。如果您打算继续使用 Cloudflare,可以将[`adapter-auto`](adapter-auto)切换为直接使用此适配器,以便在本地开发期间模拟`event.platform`,自动应用类型声明,并提供设置 Cloudflare 特定选项的能力。 ## 比较 * `adapter-cloudflare` – 支持所有 SvelteKit 功能;为 Cloudflare Workers 静态资源和 Cloudflare Pages 构建 * `adapter-cloudflare-workers` – 已弃用。支持所有 SvelteKit 功能;为 Cloudflare Workers 网站构建 * `适配器-静态` – 仅生成客户端静态资源;兼容 Cloudflare Workers 静态资源和 Cloudflare Pages ## 使用 安装 `npm i -D @sveltejs/adapter-cloudflare` 后,将适配器添加到您的`svelte.config.js`中: ```js /// file: svelte.config.js import adapter from '@sveltejs/adapter-cloudflare'; export default { kit: { adapter: adapter({ // See below for an explanation of these options config: undefined, platformProxy: { configPath: undefined, environment: undefined, persist: undefined }, fallback: 'plaintext', routes: { include: ['/*'], exclude: [''] } }) } }; ``` ## 选项 ### 配置 路径到您的[Wrangler 配置文件](https://developers.cloudflare.com/workers/wrangler/configuration/)。如果您想使用除`wrangler.jsonc`、`wrangler.json`或`wrangler.toml`之外的 Wrangler 配置文件名,可以使用此选项指定。 ### 平台代理 偏好于模拟的 `platform.env` 本地绑定。请参阅 [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API 文档以获取完整选项列表。 ### 回退 是否渲染纯文本 404.html 页面或为不匹配的资产请求渲染的 SPA 回退页面。 对于 Cloudflare Workers,默认行为是对不匹配的资产请求返回一个空体的 404 状态响应。然而,如果[`assets.not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) Wrangler 配置设置被设置为`"404-page"`,当请求失败匹配资产时,将提供此页面。如果`assets.not_found_handling`被设置为`"single-page-application"`,无论指定了什么`fallback`选项,适配器都将渲染 SPA 回退 index.html 页面。 对于 Cloudflare Pages,此页面仅在请求与`routes.exclude`中的条目不匹配且无法匹配资产时才会被提供。 大多数情况下,`plaintext`就足够了,但如果您使用`routes.exclude`手动排除一组预渲染页面且不超过 100 个路由限制,您可能希望使用`spa`来避免向用户显示未样式化的 404 页面。 查看 Cloudflare Pages 的[未找到行为](https://developers.cloudflare.com/pages/configuration/serving-pages/#not-found-behavior)获取更多信息。 ### 路线 仅适用于 Cloudflare Pages。允许您自定义由`adapter-cloudflare`生成的[`_routes.json`](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file)文件。 * `include` 定义将调用函数的路由,默认为 `['/*']` * `exclude` 定义了不会调用函数的路由 — 这是一种更快、更经济的方式来提供您的应用程序的静态资源。此数组可以包括以下特殊值: * `` 包含您的应用程序的构建工件(由 Vite 生成的文件) * `` 包含您的 `static` 目录内容 * `预渲染` 包含预渲染页面列表 * ``(默认)包含上述所有内容 您最多可以组合 100 个`include`和`exclude`规则。通常,您可以省略`routes`选项,但如果(例如)您的``路径超过该限制,您可能会发现手动创建一个包含`'/articles/*'`的`exclude`列表比自动生成的 `['/articles/foo', '/articles/bar', '/articles/baz', ...]` 更有帮助。 ## Cloudflare Workers ### 基本配置 当为 Cloudflare Workers 构建时,此适配器期望在项目根目录中找到一个[Wrangler 配置文件](https://developers.cloudflare.com/workers/configuration/sites/configuration/)。它看起来应该像这样: ```jsonc /// file: wrangler.jsonc { "name": "", "main": ".svelte-kit/cloudflare/_worker.js", "compatibility_date": "2025-01-01", "assets": { "binding": "ASSETS", "directory": ".svelte-kit/cloudflare", } } ``` ### 部署 请遵循[框架指南](https://developers.cloudflare.com/workers/frameworks/framework-guides/svelte/)开始使用 Cloudflare Workers。 ## Cloudflare Pages ### 部署 请按照[入门指南](https://developers.cloudflare.com/pages/get-started/)开始使用 Cloudflare Pages。 如果您正在使用[Git 集成](https://developers.cloudflare.com/pages/get-started/git-integration/),您的构建设置应如下所示: * 框架预设 – SvelteKit * 构建命令 – `npm run build` 或 `vite build` * 构建输出目录 – `.svelte-kit/cloudflare` ### 进一步阅读 您可能需要参考[Cloudflare 的部署 SvelteKit 网站在 Cloudflare Pages 上的文档](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/)。 ### 注释 函数包含在项目根目录下的 [`/functions` 目录](https://developers.cloudflare.com/pages/functions/routing/) 中,将 *不包括* 在部署中。相反,函数应作为您的 SvelteKit 应用中的 [服务器端点](routing#server) 实现,该应用编译为一个 [单个 `_worker.js` 文件](https://developers.cloudflare.com/pages/functions/advanced-mode/)。 ## 运行时 API 对象 [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) 包含您项目的 [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/),这些绑定包括 KV/DO 命名空间等。它通过 `platform` 属性传递给 SvelteKit,同时还包括 [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/)、[`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/) 和 [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties),这意味着您可以在钩子和端点中访问它: ```js // @errors: 7031 export async function POST({ request, platform }) { const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` > \[!注意\] 应优先使用 SvelteKit 内置的 [`$env` 模块]($env-static-private) 来处理环境变量。 为了使这些类型可用于您的应用,安装 [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) 并在您的 `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 {}; ``` ### 本地测试 Cloudflare 特定的值在 `平台` 属性中在开发和预览模式下进行模拟。根据您的 [绑定](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) 和 [Wrangler 配置文件](https://developers.cloudflare.com/workers/wrangler/) 创建本地绑定,并在开发和预览期间用于填充 `platform.env`。使用适配器配置 [`platformProxy` 选项](#Options-platformProxy) 来更改您的绑定偏好。 测试构建时,您应使用[Wrangler](https://developers.cloudflare.com/workers/wrangler/)版本 4。构建您的网站后,如果您正在测试 Cloudflare Workers,请运行 `wrangler dev .svelte-kit/cloudflare` ;如果您正在测试 Cloudflare Pages,请运行 `wrangler pages dev .svelte-kit/cloudflare` 。 ## 标题和重定向 The [`_headers`](https://developers.cloudflare.com/pages/configuration/headers/) 和 [`_redirects`](https://developers.cloudflare.com/pages/configuration/redirects/) 文件,针对 Cloudflare,可以通过将它们放入项目根目录来用于静态资源响应(如图片)。 然而,它们对由 SvelteKit 动态渲染的响应没有影响,SvelteKit 应该返回自定义头或从 [服务器端点](routing#server) 或使用 [`handle`](hooks#Server-hooks-handle) 钩子进行重定向响应。 ## 故障排除 ### Node.js 兼容性 如果您想启用[Node.js 兼容性](https://developers.cloudflare.com/workers/runtime-apis/nodejs/),您可以将`nodejs_compat`兼容性标志添加到您的 Wrangler 配置文件中: ```jsonc /// file: wrangler.jsonc { "compatibility_flags": ["nodejs_compat"] } ``` ### 工人大小限制 当部署您的应用程序时,由 SvelteKit 生成的服务器被打包成一个单独的文件。如果经过压缩后超过[大小限制](https://developers.cloudflare.com/workers/platform/limits/#worker-size),Wrangler 将无法发布您的 worker。通常情况下,您不太可能达到这个限制,但一些大型库可能会导致这种情况发生。在这种情况下,您可以尝试通过仅在客户端导入这些库来减小 worker 的大小。有关更多信息,请参阅[常见问题解答](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window)。 ### 访问文件系统 您不能在 Cloudflare Workers 中使用`fs`——您必须[预渲染](page-options#prerender)相关路由。 ## 迁移自 Workers 站点 Cloudflare 不再推荐使用[Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/configuration/),而是推荐使用[Workers Static Assets》。迁移时,将 `@sveltejs/adapter-cloudflare-workers` 替换为`@sveltejs/adapter-cloudflare`,并从您的 Wrangler 配置文件中删除所有`site`配置设置,然后添加`assets.directory`和`assets.binding`配置设置:](https://developers.cloudflare.com/workers/static-assets/) ### svelte.config.js ```js /// file: svelte.config.js ---import adapter from '@sveltejs/adapter-cloudflare-workers';--- +++import adapter from '@sveltejs/adapter-cloudflare';+++ export default { kit: { adapter: adapter() } }; ``` ### wrangler.toml ```toml /// file: wrangler.toml ---site.bucket = ".cloudflare/public"--- +++assets.directory = ".cloudflare/public" assets.binding = "ASSETS"+++ ``` ### wrangler.jsonc ```jsonc /// file: wrangler.jsonc { --- "site": { "bucket": ".cloudflare/public" },--- +++ "assets": { "directory": ".cloudflare/public", "binding": "ASSETS" }+++ } ``` # Cloudflare Workers > \[注意\] `adapter-cloudflare-workers` 已弃用,推荐使用 [`adapter-cloudflare`](adapter-cloudflare)。建议使用 `adapter-cloudflare` 将静态资源部署到 Cloudflare Workers,因为 Cloudflare Workers 网站将弃用,推荐使用它。 要部署到[Cloudflare Workers](https://workers.cloudflare.com/)和[Workers Sites](https://developers.cloudflare.com/workers/configuration/sites/),请使用`adapter-cloudflare-workers`。 ## 使用 安装 `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({ // see below for options that can be set here }) } }; ``` ## 选项 ### 配置 路径到您的[Wrangler 配置文件](https://developers.cloudflare.com/workers/wrangler/configuration/)。如果您想使用除`wrangler.jsonc`、`wrangler.json`或`wrangler.toml`之外的 Wrangler 配置文件名,可以使用此选项指定。 ### 平台代理 偏好于模拟的 `platform.env` 本地绑定。请参阅 [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API 文档以获取完整选项列表。 ## 基本配置 此适配器期望在项目根目录中找到一个[Wrangler 配置文件](https://developers.cloudflare.com/workers/configuration/sites/configuration/)。它应该看起来像这样: ```jsonc /// file: wrangler.jsonc { "name": "", "account_id": "", "main": "./.cloudflare/worker.js", "site": { "bucket": "./.cloudflare/public" }, "build": { "command": "npm run build" }, "compatibility_date": "2021-11-12" } ``` `您的服务名称`可以是任何内容。`您的账户 ID`可以通过运行`wrangler whoami`使用 Wrangler CLI 工具或通过登录您的[Cloudflare 控制台](https://dash.cloudflare.com)并从 URL 末尾获取。 ``` https://dash.cloudflare.com//home ``` > \[!注意\] 您应将 `.cloudflare` 目录(或您指定的 `main` 和 `site.bucket` 目录)以及 `.wrangler` 目录添加到您的 `.gitignore` 中。 您需要安装[Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/)并登录,如果您还没有的话: ```bash npm i -D wrangler wrangler login ``` 然后,您可以构建您的应用程序并部署它: `wrangler 部署` ## 运行时 API 对象 [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) 包含您项目的 [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/),这些绑定包括 KV/DO 命名空间等。它通过 `platform` 属性传递给 SvelteKit,同时还包括 [`ctx`](https://developers.cloudflare.com/workers/runtime-apis/context/)、[`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/) 和 [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties),这意味着您可以在钩子和端点中访问它: ```js // @errors: 7031 export async function POST({ request, platform }) { const x = platform.env.YOUR_DURABLE_OBJECT_NAMESPACE.idFromName('x'); } ``` > \[!注意\] 应优先使用 SvelteKit 内置的 [`$env` 模块]($env-static-private) 来处理环境变量。 为了使这些类型可用于您的应用,安装 [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) 并在您的 `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 {}; ``` ### 本地测试 Cloudflare Workers 在`平台`属性中的特定值在开发和预览模式下进行模拟。根据您的[Wrangler 配置文件](https://developers.cloudflare.com/workers/wrangler/)创建本地[绑定](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings),并在开发和预览期间用于填充`platform.env`。使用适配器配置中的[`platformProxy`选项](#Options-platformProxy)来更改绑定偏好。 测试构建时,您应使用[Wrangler](https://developers.cloudflare.com/workers/wrangler/)版本 4。构建您的网站后,运行`wrangler dev`。 ## 故障排除 ### Node.js 兼容性 如果您想启用[Node.js 兼容性](https://developers.cloudflare.com/workers/runtime-apis/nodejs/),您可以将`nodejs_compat`兼容性标志添加到您的 Wrangler 配置文件中: ```jsonc /// file: wrangler.jsonc { "compatibility_flags": ["nodejs_compat"] } ``` ### 工人大小限制 当部署您的应用程序时,由 SvelteKit 生成的服务器被打包成一个单独的文件。如果经过压缩后超过[大小限制](https://developers.cloudflare.com/workers/platform/limits/#worker-size),Wrangler 将无法发布您的 worker。通常情况下,您不太可能达到这个限制,但一些大型库可能会导致这种情况发生。在这种情况下,您可以尝试通过仅在客户端导入这些库来减小 worker 的大小。有关更多信息,请参阅[常见问题解答](./faq#How-do-I-use-a-client-side-library-accessing-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: { // default options are shown adapter: adapter({ // if true, will create a Netlify Edge Function rather // than using standard Node-based functions edge: false, // if true, will split your app into multiple functions // instead of creating a single one for the entire app. // if `edge` is true, this option cannot be used 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 LTS 版本。然而,如果您升级的是您很久以前创建的项目,它可能卡在较旧版本上。请参阅[Netlify 文档](https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript)以获取有关手动指定当前 Node 版本的详细信息。 ## Netlify Edge Functions SvelteKit 支持 [Netlify Edge Functions](https://docs.netlify.com/netlify-labs/experimental-features/edge-functions/)。如果您将选项 `edge: true` 传递给 `adapter` 函数,服务器端渲染将在部署在接近网站访客的基于 Deno 的边缘函数中发生。如果设置为 `false`(默认值),网站将部署到基于 Node 的 Netlify Functions。 ```js // @errors: 2307 /// file: svelte.config.js import adapter from '@sveltejs/adapter-netlify'; export default { kit: { adapter: adapter({ // will create a Netlify Edge Function using Deno-based // rather than using standard Node-based functions edge: true }) } }; ``` ## Netlify 的 SvelteKit 功能替代方案 您可以使用 SvelteKit 直接提供的功能来构建您的应用程序,无需依赖任何 Netlify 功能。使用这些功能的 SvelteKit 版本将允许它们在开发模式下使用,通过集成测试进行测试,并在您决定从 Netlify 切换时与其他适配器一起工作。然而,在某些情况下,使用这些功能的 Netlify 版本可能更有益。一个例子是如果您正在将已托管在 Netlify 上的应用程序迁移到 SvelteKit。 ### `_headers` 和 `_redirects` The [`_headers`](https://docs.netlify.com/routing/headers/#syntax-for-the-headers-file) 和 [`_redirects`](https://docs.netlify.com/routing/redirects/redirect-options/) 文件是针对 Netlify 的,可以将它们放入项目根目录以用于静态资源响应(如图片)。 ### 重定向规则 在编译过程中,重定向规则会自动添加到您的 `_redirects` 文件中。(如果尚不存在,则会创建。)这意味着: * `[[重定向]]` 在 `netlify.toml` 中永远不会与 `_redirects` 匹配,因为 [优先级更高](https://docs.netlify.com/routing/redirects/#rule-processing-order)。所以请始终将您的规则放在 [`_redirects` 文件](https://docs.netlify.com/routing/redirects/#syntax-for-the-redirects-file) 中。 * `_redirects` 不应包含任何自定义的 "catch all" 规则,例如 `/* /foobar/:splat`。否则,自动附加的规则将永远不会被应用,因为 Netlify 只处理 [第一个匹配的规则](https://docs.netlify.com/routing/redirects/#rule-processing-order)。 ### Netlify 表单 1. 创建您的 Netlify HTML 表单,如[此处](https://docs.netlify.com/forms/setup/#html-forms)所述,例如:`/routes/contact/+page.svelte`。(别忘了添加隐藏的`form-name`输入元素!) 2. Netlify 的构建机器人会在部署时解析您的 HTML 文件,这意味着您的表单必须作为 HTML 进行[预渲染](page-options#prerender)。您可以选择将`export const prerender = true`添加到您的`contact.svelte`中,以仅预渲染该页面,或者设置`kit.prerender.force: true`选项以预渲染所有页面。 3. 如果您 Netlify 表单有一个[自定义成功消息](https://docs.netlify.com/forms/setup/#success-messages)如 `` ,那么请确保相应的`/routes/success/+page.svelte`存在并且已预渲染。 ### Netlify 函数 使用此适配器,SvelteKit 端点作为[Netlify Functions](https://docs.netlify.com/functions/overview/)托管。Netlify 函数处理程序具有额外的上下文,包括[Netlify Identity](https://docs.netlify.com/visitor-access/identity/)信息。您可以通过在您的钩子中的`event.platform.context`字段或`+page.server`或`+layout.server`端点中访问此上下文。当适配器配置中的`edge`属性为`false`时,这些是[无服务器函数](https://docs.netlify.com/functions/overview/);当它为`true`时,则是[边缘函数](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); // shows up in your functions log in the Netlify app }; ``` 另外,您可以通过创建目录并为它们添加配置到您的 `netlify.toml` 文件中来添加自己的 Netlify 函数。例如: ```toml [build] command = "npm run build" publish = "build" [functions] directory = "functions" ``` ## 故障排除 ### 访问文件系统 您不能在边缘部署中使用`fs`。 您可以使用它进行无服务器部署,但由于文件没有从您的项目复制到您的部署中,它可能不会按预期工作。相反,请使用来自 `$app/server` 的 [`read`]($app-server#read) 函数来访问您的文件。`read` 在边缘部署中不起作用(这可能在将来改变)。 或者,您也可以[预渲染](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({ // see below for options that can be set here }) } }; ``` ## 部署配置 要控制您的路由如何作为函数部署到 Vercel,您可以通过上述选项或通过在`+server.js`、`+page(.server).js`和`+layout(.server).js`文件内的`export const config`进行部署配置。 例如,您可以将应用程序的一些部分部署为[边缘函数](https://vercel.com/docs/concepts/functions/edge-functions)... ```js /// file: about/+page.js /** @type {import('@sveltejs/adapter-vercel').Config} */ export const config = { runtime: 'edge' }; ``` ...以及其他,如[无服务器函数](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' }; ``` 以下选项适用于所有功能: * `运行时`:`'edge'`,`'nodejs18.x'`,`'nodejs20.x'` 或 `'nodejs22.x'`。默认情况下,适配器将选择与您在 Vercel 控制台配置的项目使用的 Node 版本相对应的 `'nodejs.x'`。 * `regions`:一个包含[边缘网络区域](https://vercel.com/docs/concepts/edge-network/regions)的数组(对于无服务器函数默认为`["iad1"]`)或当`runtime`为`edge`(其默认值)时为`'all'`。请注意,对于无服务器函数,仅在企业计划中支持多个区域。 * `split`:如果`true`,则将路由部署为单个函数。如果`split`在适配器级别设置为`true`,则所有路由都将部署为单个函数 此外,以下选项适用于边缘函数: * `外部`:esbuild 在打包函数时应将其视为外部的依赖数组。这仅应用于排除不会在 Node 外部运行的可选依赖项。 以下选项适用于无服务器函数: * `内存`:函数可用的内存量。默认为`1024` Mb,可降低至`128` Mb 或[增加](https://vercel.com/docs/concepts/limits/overview#serverless-function-memory),以 64Mb 的增量增加,最高可达`3008` Mb(在 Pro 或企业账户上)。 * `maxDuration`:[最大执行时间](https://vercel.com/docs/functions/runtimes#max-duration)的函数。默认为 Hobby 账户`10`秒,Pro 账户`15`秒,企业账户`900`秒。 * `isr`:配置增量静态再生,如下所述 如果您的函数需要访问特定区域的数据,建议将它们部署在同一区域(或附近)以获得最佳性能。 ## 图片优化 您可以将`图片`配置设置为控制 Vercel 如何构建您的图片。请参阅[图片配置参考](https://vercel.com/docs/build-output-api/v3/configuration#images)以获取完整详情。例如,您可以设置: ```js /// file: svelte.config.js import adapter from '@sveltejs/adapter-vercel'; export default { kit: { adapter: 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 的路由上应用 ISR,在这些路由上每个访客都应该看到相同的内容(就像在预渲染时一样)。如果发生任何特定于用户的情况(如会话 cookie),它们应该在客户端通过 JavaScript 仅发生,以避免在访问之间泄露敏感信息 要向路由添加 ISR,请在您的`config`对象中包含`isr`属性: ```js // @errors: 2664 import { BYPASS_TOKEN } from '$env/static/private'; export const config = { isr: { expiration: 60, bypassToken: BYPASS_TOKEN, allowQuery: ['search'] } }; ``` > 使用 ISR 在具有`export const prerender = true`的路由上不会有任何效果,因为该路由在构建时已预渲染 The `expiration` 属性是必需的;其他所有属性都是可选的。这些属性将在下面更详细地讨论。 ### 到期 过期时间(以秒为单位)在通过调用无服务器函数重新生成缓存资源之前。将值设置为`false`表示它永远不会过期。在这种情况下,您可能希望定义一个绕过令牌以按需重新生成。 ### 绕过令牌 一个随机令牌,可以提供在 URL 中以请求带有`__prerender_bypass=` cookie 的资产来绕过资产的缓存版本。 制作一个 `GET` 或 `HEAD` 请求与 `x-prerender-revalidate: ` 将强制资产重新验证。 请注意,`BYPASS_TOKEN`字符串必须至少 32 个字符长。您可以使用 JavaScript 控制台生成一个,如下所示: ```js crypto.randomUUID(); ``` 将此字符串设置为 Vercel 上的环境变量,通过登录并进入您的项目然后设置 > 环境变量。在“键”中输入`BYPASS_TOKEN`,在“值”中使用上面生成的字符串,然后点击“保存”。 要使此密钥在本地开发中为人所知,您可以使用 [Vercel CLI](https://vercel.com/docs/cli/env),通过在本地运行`vercel env pull`命令来实现,如下所示: ```bash vercel env pull .env.development.local ``` ### 允许查询 有效查询参数列表,这些参数有助于生成缓存键。其他参数(如 utm 跟踪代码)将被忽略,确保它们不会导致内容不必要地重新生成。默认情况下,查询参数将被忽略。 > 页面中[预渲染](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

This staging environment was deployed from {data.deploymentGitBranch}.

``` 由于所有这些变量在 Vercel 构建时和运行时都没有变化,我们建议使用`$env/static/private`——这将静态替换变量,启用诸如死代码消除等优化——而不是使用`$env/dynamic/private`。 ## 倾斜保护 当您的应用部署新版本时,旧版本的资产可能不再可访问。如果用户在此期间正在使用您的应用,导航时可能会出现错误——这被称为*版本偏差*。SvelteKit 通过检测由版本偏差引起的错误并强制重新加载以获取最新版本的应用来减轻这一问题,但这将导致任何客户端状态丢失。(您也可以通过观察来自`$app/state`的`[updated.current]($app-state#updated)`来主动减轻这一问题,它告诉客户端何时部署了新版本。) [偏斜保护](https://vercel.com/docs/deployments/skew-protection)是 Vercel 的一项功能,它将客户端请求路由到其原始部署。当用户访问您的应用时,会设置一个包含部署 ID 的 cookie,并且只要偏斜保护处于活动状态,任何后续请求都将路由到该部署。当他们重新加载页面时,他们将获得最新的部署。(`updated.current`不受此行为影响,将继续报告新部署。)要启用它,请访问 Vercel 项目设置的“高级”部分。 基于 Cookie 的偏斜保护有一个注意事项:如果用户在多个标签页中打开了您的应用的不同版本,旧版本的请求将被路由到新版本,这意味着它们将回退到 SvelteKit 的内置偏斜保护。 ## 注释 ### Vercel 函数 如果您在项目根目录的 `api` 目录中包含 Vercel 函数,则对 `/api/*` 的任何请求将不会被 SvelteKit 处理。您应该在您的 SvelteKit 应用中实现这些作为 [API 路由](routing#server),除非您需要使用非 JavaScript 语言,在这种情况下,您需要确保您的 SvelteKit 应用中没有任何 `/api/*` 路由。 ### 节点版本 项目在某个日期之前创建的可能会默认使用比 SvelteKit 当前要求的旧版 Node 版本。您可以在[项目设置中更改 Node 版本](https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js#node.js-version)。 ## 故障排除 ### 访问文件系统 您不能在边缘函数中使用`fs`。 您可以使用它来在无服务器函数中,但由于文件没有从您的项目复制到您的部署中,它可能不会按预期工作。相反,请使用来自 `$app/server` 的 [`read`]($app-server#read) 函数来访问您的文件。`read` 在作为边缘函数部署的路由中不起作用(这可能在将来改变)。 或者,您也可以[预渲染](page-options#prerender)相关的路由。 # Writing adapters 如果您的首选环境还没有适配器,您可以自己构建。我们建议查看[类似您平台的适配器源代码](https://github.com/sveltejs/kit/tree/main/packages)作为起点进行复制。 适配器包实现了以下 API,该 API 创建了一个`适配器`: ```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) { // adapter implementation }, async emulate() { return { async platform({ config, prerender }) { // the returned object becomes `event.platform` during dev, build and // preview. Its shape is that of `App.Platform` } } }, supports: { read: ({ config, route }) => { // Return `true` if the route with the given `config` can use `read` // from `$app/server` in production, return `false` if it can't. // Or throw a descriptive error describing how to configure the deployment } } }; return adapter; } ``` 在这些中,`名称`和`适配`是必需的。`模拟`和`支持`是可选的。 在`adapt`方法中,适配器应该做很多事情: * 清理构建目录 * 编写 SvelteKit 输出,使用`builder.writeClient`、`builder.writeServer`和`builder.writePrerendered` * 输出代码,使其: * 导入 `服务器` 从 `${builder.getServerDirectory()}/index.js` * 实例化应用程序,使用 `builder.generateManifest({ relativePath })` 生成的清单 * 监听来自平台的请求,如有必要,将其转换为标准 [`请求`](https://developer.mozilla.org/en-US/docs/Web/API/Request),调用 `server.respond(request, { getClientAddress })` 函数生成 [`响应`](https://developer.mozilla.org/en-US/docs/Web/API/Response) 并以之响应 * 暴露任何平台特定信息到 SvelteKit,通过传递给 `platform` 选项的 `server.respond`。 * 全局适配 `fetch` 以在目标平台上运行,如有必要。SvelteKit 为可使用 `@sveltejs/kit/node/polyfills` 的平台提供了一个 `undici` 帮助器。 * 将输出打包以避免在目标平台上需要安装依赖项,如果需要 * 将用户的静态文件和生成的 JS/CSS 放置在目标平台的正确位置 尽可能的情况下,我们建议将适配器输出放在`build/`目录下,任何中间输出放在`.svelte-kit/[adapter-name]`目录下。 # Advanced routing ## 剩余参数 如果路由段的数量未知,您可以使用剩余语法 - 例如,您可能可以这样实现 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' } ``` > \[!注意\] `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/+error.svelte` 将不会被渲染,如果您访问 `/marx-brothers/karl`,因为没有匹配到路由。如果您想渲染嵌套的错误页面,您应该创建一个匹配任何 `/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'); } ``` > \[注意\] 如果您不处理 404 情况,它们将在[`handleError`](hooks#Shared-hooks-handleError)中显示 ## 可选参数 一条类似于 `[lang]/home` 的路由包含一个名为 `lang` 的参数,该参数是必需的。有时将这些参数设置为可选是有益的,因此在这个例子中,`home` 和 `en/home` 都指向同一页面。您可以通过将参数包裹在另一对括号中来实现这一点:`[[lang]]/home` 请注意,可选路由参数不能紧接在 rest 参数之后(`[...rest]/[[optional]]`),因为参数是“贪婪”匹配的,可选参数将始终未被使用。 ## 匹配 一条类似于 `src/routes/fruits/[page]` 的路由会匹配 `/fruits/apple`,但也会匹配 `/fruits/rocketship`。我们不希望这样。您可以通过添加一个 *matcher* 来确保路由参数格式正确——该匹配器接受参数字符串(`"apple"` 或 `"rocketship"`)并返回 `true` 如果它是有效的——到您的 [`params`](configuration#files) 目录... ```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`文件。 > \[!注意\] 匹配器在服务器和浏览器上同时运行。 ## 排序 可能多个路由会匹配给定的路径。例如,以下每个路由都会匹配 `/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]`)的参数 * `[[可选]]` 和 `[...其余]` 参数除非是路由的最后一部分,否则将被忽略,在这种情况下,它们将被赋予最低优先级。换句话说,`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', hence '[x+3a]' ``` 您也可以使用 Unicode 转义序列。通常情况下,您不需要这样做,因为您可以直接使用未编码的字符,但如果——出于某种原因——您不能使用包含表情符号的文件名,例如,那么您可以使用转义字符。换句话说,这些是等效的: ``` src/routes/[u+d83e][u+dd2a]/+page.svelte src/routes/🤪/+page.svelte ``` Unicode 转义序列的格式为`[u+nnnn]`,其中`nnnn`是介于`0000`和`10ffff`之间的有效值。(与 JavaScript 字符串转义不同,表示大于`ffff`的码点时无需使用代理对。)要了解更多关于 Unicode 编码的信息,请参阅[《Unicode 编程》](https://unicodebook.readthedocs.io/unicode_encodings.html)。 > \[!注意\] 由于 TypeScript [难以处理](https://github.com/microsoft/TypeScript/issues/13399) 以点号开头的目录,您在创建例如 [`.well-known`](https://en.wikipedia.org/wiki/Well-known_URI) 路由时可能发现对这些字符进行编码很有用: `src/routes/[x+2e]well-known/...` ## 高级布局 默认情况下,*布局层次结构*与*路由层次结构*相匹配。在某些情况下,这可能不是您想要的结果。 ### (組) 可能您有一些路由是 'app' 路由,应该使用一个布局(例如 `/dashboard` 或 `/item`),而其他的是 'marketing' 路由,应该使用不同的布局(`/about` 或 `/testimonials`)。我们可以将这些路由分组到一个以括号包裹的目录中——与普通目录不同,`(app)` 和 `(marketing)` 不会影响它们内部路由的 URL 路径名: ```tree src/routes/ +++│ (app)/+++ │ ├ dashboard/ │ ├ item/ │ └ +layout.svelte +++│ (marketing)/+++ │ ├ about/ │ ├ testimonials/ │ └ +layout.svelte ├ admin/ └ +layout.svelte ``` 您也可以直接在`(group)`中放置一个`+page`,例如,如果`/`应该是一个`(app)`或`(marketing)`页面。 ### 跳出布局 根布局适用于您应用的每个页面 — 如果省略,则默认为 `render children()`。如果您想某些页面具有与其他页面不同的布局层次结构,则可以将整个应用放入一个或多个组 *除外* 应该继承公共布局的路由。 在上述示例中,`/admin` 路由既不继承 `(app)` 也不继承 `(marketing)` 布局。 ### +页面@ 页面可以在路由级别逐个突破当前布局层次结构。假设我们有一个位于上一个示例中的 `(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@.svelte`组件将重置其所有子路由的层次结构。 ``` src/routes/ ├ (app)/ │ ├ item/ │ │ ├ [id]/ │ │ │ ├ embed/ │ │ │ │ └ +page.svelte // uses (app)/item/[id]/+layout.svelte │ │ │ ├ +layout.svelte // inherits from (app)/item/+layout@.svelte │ │ │ └ +page.svelte // uses (app)/item/+layout@.svelte │ │ └ +layout@.svelte // inherits from root layout, skipping (app)/+layout.svelte │ └ +layout.svelte └ +layout.svelte ``` ### 何时使用布局组 不是所有用例都适合布局分组,也不必强迫自己使用它们。可能你的用例会导致复杂的`(分组)`嵌套,或者你不想为单个异常引入一个`(分组)`。使用其他方法,如组合(可重用的`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 钩子是你在应用程序范围内声明的函数,SvelteKit 会在响应特定事件时调用这些函数,从而让你能够精细控制框架的行为。 有三个钩子文件,都是可选的: * `src/hooks.server.js` — 您的应用程序的服务器钩子 * `src/hooks.client.js` — 您的应用程序客户端钩子 * `src/hooks.js` — 您的应用程序在客户端和服务器上运行的钩子 代码在这些模块中将在应用程序启动时运行,使它们对于初始化数据库客户端等非常有用。 > \[!注意\] 您可以使用[`config.kit.files.hooks`](configuration#files)配置这些文件的存储位置。 ## 服务器钩子 以下钩子可以添加到 `src/hooks.server.js` 中: ### 处理 这个函数在 SvelteKit 服务器每次接收到[请求](web-standards#Fetch-APIs-Request)时都会运行——无论是在应用运行期间,还是在[预渲染](page-options#prerender)期间——并确定[响应](web-standards#Fetch-APIs-Response)。它接收一个表示请求的``` 事件对象和一个名为`` 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; } ``` > \[注意\] 对于静态资源请求——包括已预渲染的页面——SvelteKit *不*进行处理。 如果未实现,则默认为 `({ event, resolve }) => resolve(event)` 。 在预渲染期间,SvelteKit 会爬取您的页面以查找链接,并为找到的每个路由进行渲染。渲染路由会调用`handle`函数(以及所有其他路由依赖项,如`load`)。如果您需要在此阶段排除某些代码的运行,请确保应用不是在[`构建`]($app-environment#building)。 ### 本地 要向请求中添加自定义数据,这些数据将传递到在 `+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); // Note that modifying response headers isn't always safe. // Response objects can have immutable headers // (e.g. Response.redirect() returned from an endpoint). // Modifying immutable headers throws a TypeError. // In that case, clone the response or avoid creating a // response object with immutable headers. response.headers.set('x-custom-header', 'potato'); return response; } ``` 您可以为多个 `handle` 函数定义,并使用 [序列 `helper` 函数](@sveltejs-kit-hooks) [执行它们](@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 将以错误或备用错误页面的 JSON 表示形式响应——该页面可以通过`src/error.html`进行自定义——具体取决于`Accept`头。您可以在[这里](errors)了解更多关于错误处理的信息。 ### 处理获取 此功能允许您修改(或替换)在端点内运行的 [`event.fetch`](load#Making-fetch-requests) 调用的结果,该调用在服务器上(或在预渲染期间)执行,包括 `load`、`action`、`handle`、`handleError` 或 `reroute`。 例如,您的 `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/')) { // clone the original request, but change the URL request = new Request( request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'), request ); } return fetch(request); } ``` 请求使用`event.fetch`的请求遵循浏览器的凭据模型——对于同源请求,除非将`credentials`选项设置为`"omit"`,否则将转发`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); } ``` ## 共享钩子 以下可以添加到 `src/hooks.server.js`*和*`src/hooks.client.js`: ### 处理错误 如果在加载、渲染或从端点抛出[意外错误](errors#Unexpected-errors)时,此函数将使用`错误`、`事件`、`状态`代码和`消息`被调用。这允许做两件事: * 您可以记录错误 * 您可以为用户生成一个安全的错误自定义表示,省略敏感细节,如消息和堆栈跟踪。返回的值默认为`{消息}`,成为`$page.error`的值。 对于从您的代码(或被您的代码调用的库代码)抛出的错误,状态将为 500,信息为“内部错误”。虽然`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(); // example integration with https://sentry.io/ Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: 'Whoops!', 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(); // example integration with https://sentry.io/ Sentry.captureException(error, { extra: { event, errorId, status } }); return { message: 'Whoops!', errorId }; } ``` > \[!注意\] 在 `src/hooks.client.js` 中,`handleError` 的类型为 `HandleClientError` 而不是 `HandleServerError`,并且 `event` 是 `NavigationEvent` 而不是 `RequestEvent`。 此函数不会为*预期*错误(使用从`@sveltejs/kit`导入的`error`函数抛出的错误)调用。 在开发过程中,如果由于您的 Svelte 代码中的语法错误导致发生错误,传入的错误将附加一个`frame`属性,突出显示错误的位置。 > \[!注意\] 确保以下代码 `handleError`*从不* 抛出错误 ### 初始化 此函数在服务器创建或浏览器中的应用启动时运行一次,是执行异步工作(如初始化数据库连接)的有用位置。 > \[注意\] 如果您的环境支持顶级 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(); } ``` > \[!注意\] 在浏览器中,`init` 中的异步工作将延迟初始化,因此请注意您放入那里的内容。 ## 通用钩子 以下可以添加到 `src/hooks.js`。通用钩子在服务器和客户端上运行(不要与特定环境的共享钩子混淆)。 ### 重定向 此函数在 `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]; } } ``` The `lang` parameter will be correctly derived from the returned pathname. 使用`reroute`不会改变浏览器地址栏的内容,或`event.url`的值。*不会* 自 2.18 版本起,`reroute`钩子可以异步执行,允许它(例如)从您的后端获取数据以决定重定向到何处。请谨慎使用,并确保其快速执行,否则它将延迟导航。如果您需要获取数据,请使用作为参数提供的`fetch`。它与提供给`load`函数的`fetch`具有相同的优势,但有一个前提是`params`和`id`对于[`handleFetch`](#Server-hooks-handleFetch)不可用,因为路由尚未确定。 ```js /// file: src/hooks.js // @errors: 2345` // @errors: 2304 /** @type {import('@sveltejs/kit').Reroute} */ export async function reroute({ url, fetch }) { // Ask a special endpoint within your app about the destination if (url.pathname === '/api/reroute') return; const api = new URL('/api/reroute', url); api.searchParams.set('pathname', url.pathname); const result = await fetch(api).then(r => r.json()); return result.pathname; } ``` > \[注意\] `reroute` 被视为一个纯净的幂等函数。因此,它必须始终对相同的输入返回相同的输出,并且不能有副作用。在这些假设下,SvelteKit 在客户端缓存了`reroute`的结果,因此它只对每个唯一的 URL 调用一次。 ### 运输 这是一个包含 *运输器* 的集合,允许您传递自定义类型——从 `加载` 和表单操作返回的类型——跨越服务器/客户端边界。每个运输器包含一个 `编码` 函数,该函数在服务器上编码值(或对于不是该类型实例的任何内容返回一个假值)以及相应的 `解码` 函数: ```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) } }; ``` ## 进一步阅读 * [ 教程:钩子](/tutorial/kit/handle) # Errors 错误是软件开发不可避免的事实。SvelteKit 根据错误发生的位置、错误类型以及请求的性质来处理错误。 ## 错误对象 SvelteKit 区分预期错误和意外错误,这两者默认都表示为简单的 `{ message: string }` 对象。 您可以添加额外的属性,例如一个 `代码` 或一个跟踪的 `id`,如下面的示例所示。(当使用 TypeScript 时,这需要您按照 [类型安全](errors#Type-safety) 的描述重新定义 `Error` 类型)。 ## 预期错误 一个*预期的*错误是指使用从`@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: 'Not found' }); } return { post }; } ``` 这会抛出一个异常,SvelteKit 会捕获它,导致它将响应状态码设置为 404,并渲染一个[`+error.svelte`](routing#error)组件,其中`page.error`是作为第二个参数提供给`error(...)`的对象。 ```svelte

{page.error.message}

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

My custom error page

Status: %sveltekit.status%

Message: %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) * [ 教程:钩子](/tutorial/kit/handle) # Link options 在 SvelteKit 中,使用`
`元素(而不是特定框架的``组件)在应用程序的路由之间进行导航。如果用户点击的链接的`href`属于应用程序(例如,与指向外部网站的链接相对),则 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` 开始。 * `"点击"` 表示一旦注册了 `touchstart` 或 `mousedown` 事件,预加载就会开始 默认项目模板在 `src/app.html` 中的 `` 元素上应用了 `data-sveltekit-preload-data="hover"` 属性,意味着默认情况下每个链接在悬停时都会预先加载: ```html
%sveltekit.body%
``` 有时,当用户悬停在链接上时调用 `load` 可能不理想,要么是因为它可能导致误报(点击不必跟随悬停)或因为数据更新非常快,延迟可能导致数据过时。 在这些情况下,您可以指定 `"tap"` 值,这将导致 SvelteKit 仅在用户点击或点击链接时调用 `load`: ```html
Get current stonk values ``` > \[!注意\] 您还可以通过程序调用来自 `$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"` - 如上,除了代码是预加载的 * `"点击"` - 如上,除了代码是预加载的 请注意,`viewport` 和 `eager` 只适用于导航后立即存在于 DOM 中的链接——如果后来添加了链接(例如在 `{#if ...}` 块中),则它将不会在触发 `hover` 或 `tap` 之前预加载。这是为了避免因积极观察 DOM 变化而产生的性能问题。 > \[注意\] 由于预加载代码是预加载数据的先决条件,此属性只有在指定比任何存在的 `data-sveltekit-preload-data` 属性更急迫的值时才会生效。 与`data-sveltekit-preload-data`一样,如果用户选择了减少数据使用,则此属性将被忽略。 ## data-sveltekit-reload 偶尔,我们需要告诉 SvelteKit 不要处理链接,但允许浏览器处理它。给链接添加一个 `data-sveltekit-reload` 属性... ```html Path ``` ...点击链接将导致全页导航。 链接具有 `rel="external"` 属性的将接受相同的处理。此外,它们将在 [预渲染](page-options#prerender) 过程中被忽略。 ## data-sveltekit-replacestate 有时您不希望导航在浏览器会话历史中创建新条目。将一个 `data-sveltekit-replacestate` 属性添加到链接中... ```html Path ``` ...將替换单个当前`历史`条目,而不是在点击链接时使用`pushState`创建一个新的条目。 ## data-sveltekit-keepfocus 有时您不希望在导航后重置[焦点](accessibility#Focus-management)。例如,可能您有一个在用户输入时提交的搜索表单,并且您希望保持文本输入的焦点。向其中添加一个`data-sveltekit-keepfocus`属性... ```html
``` ...將導致當前聚焦的元素在導航後保持聚焦。通常,請避免在鏈接上使用此屬性,因為聚焦的元素將是``標籤(而不是之前聚焦的元素),並且屏幕讀取器和其他輔助技術用戶通常期望在導航後移動聚焦。您還應該只在導航後仍然存在的元素上使用此屬性。如果元素不再存在,用戶的聚焦將會丟失,這會為輔助技術用戶帶來令人困惑的體驗。 ## data-sveltekit-noscroll 当导航到内部链接时,SvelteKit 会模仿浏览器的默认导航行为:它会将滚动位置更改为 0,0,使用户位于页面的最左上角(除非链接包含一个`#hash`,在这种情况下,它将滚动到具有匹配 ID 的元素)。 在某些情况下,您可能希望禁用此行为。向链接添加一个 `data-sveltekit-noscroll` 属性... ```html Path ``` ...点击链接后将阻止滚动。 ## 禁用选项 要禁用元素内已启用的任何这些选项,请使用 `"false"` 值: ```html
a b c
d e f
``` 为条件性地将属性应用于元素,请这样做: ```svelte
``` # Service workers 服务工作者充当代理服务器,处理您应用内的网络请求。这使得您的应用能够在离线状态下运行,即使您不需要离线支持(或者由于您正在构建的应用类型而无法实际实现),通常也值得使用服务工作者通过预缓存您的 JS 和 CSS 来加速导航。 在 SvelteKit 中,如果您有一个`src/service-worker.js`文件(或`src/service-worker/index.js`),它将被打包并自动注册。如果您需要,可以更改[您的服务工作者位置](configuration#files)。 您可以根据需要禁用自动注册[,如果您需要使用自己的逻辑或另一个解决方案来注册服务工作者。默认注册看起来可能像这样:](configuration#serviceWorker) ```js if ('serviceWorker' in navigator) { addEventListener('load', function () { navigator.serviceWorker.register('./path/to/service-worker.js'); }); } ``` ## 服务工作者内部 在服务工作者内部,您可以访问 [`$service-worker` 模块]($service-worker),该模块为您提供了所有静态资源、构建文件和预渲染页面的路径。您还提供了一个应用程序版本字符串,您可以使用它来创建唯一的缓存名称,以及部署的 `base` 路径。如果您的 Vite 配置指定了 `define`(用于全局变量替换),这将应用于服务工作者以及您的服务器/客户端构建。 以下示例会预先缓存构建的应用程序以及`static`中的任何文件,并且会按发生顺序缓存所有其他请求。这样,一旦访问过每个页面,就可以离线工作。 ```js // @errors: 2339 /// import { build, files, version } from '$service-worker'; // Create a unique cache name for this deployment const CACHE = `cache-${version}`; const ASSETS = [ ...build, // the app itself ...files // everything in `static` ]; self.addEventListener('install', (event) => { // Create a new cache and add all files to it async function addFilesToCache() { const cache = await caches.open(CACHE); await cache.addAll(ASSETS); } event.waitUntil(addFilesToCache()); }); self.addEventListener('activate', (event) => { // Remove previous cached data from disk async function deleteOldCaches() { for (const key of await caches.keys()) { if (key !== CACHE) await caches.delete(key); } } event.waitUntil(deleteOldCaches()); }); self.addEventListener('fetch', (event) => { // ignore POST requests etc if (event.request.method !== 'GET') return; async function respond() { const url = new URL(event.request.url); const cache = await caches.open(CACHE); // `build`/`files` can always be served from the cache if (ASSETS.includes(url.pathname)) { const response = await cache.match(url.pathname); if (response) { return response; } } // for everything else, try the network first, but // fall back to the cache if we're offline try { const response = await fetch(event.request); // if we're offline, fetch can return a value that is not a Response // instead of throwing - and we can't pass this non-Response to 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; } // if there's no cache, then just error out // as there is nothing we can do to respond to this request throw err; } } event.respondWith(respond()); }); ``` > \[注意\] 缓存时请小心!在某些情况下,过时的数据可能比离线时不可用的数据更糟。由于浏览器会在缓存太满时清空缓存,因此您还应注意缓存大型资产,如视频文件。 ## 开发期间 服务工作者在生产环境中被打包,但在开发期间不会。因此,只有支持在服务工作者中使用[模块](https://web.dev/es-modules-in-sw)的浏览器才能在开发时间使用它们。如果您手动注册服务工作者,您需要在开发时传递`{ type: 'module' }`选项: ```js import { dev } from '$app/environment'; navigator.serviceWorker.register('/service-worker.js', { type: dev ? 'module' : 'classic' }); ``` > \[!注意\] `build` 和 `prerendered` 在开发期间为空数组 ## 类型安全 设置服务工作者适当类型需要一些手动设置。在您的 `service-worker.js` 文件中,将以下内容添加到文件顶部: ```original-js /// /// /// /// const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self)); ``` ```generated-ts /// /// /// /// const sw = self as unknown as ServiceWorkerGlobalScope; ``` 此操作禁用了对 DOM 类型定义的访问,例如`HTMLElement`,这些在服务工作者内部不可用,并实例化了正确的全局变量。将`self`重新赋值为`sw`允许你在过程中进行类型转换(有几种方法可以做到这一点,但这是最简单的一种,不需要额外的文件)。在文件的其他部分使用`sw`代替`self`。对 SvelteKit 类型的引用确保了`$service-worker`导入有正确的类型定义。如果你导入`$env/static/public`,你必须使用`// @ts-ignore`忽略导入或添加 `/// ` 到引用类型。 ## 其他解决方案 SvelteKit 的服务工作者实现旨在易于使用,可能是大多数用户的良好解决方案。然而,在 SvelteKit 之外,许多 PWA 应用程序利用了[Workbox](https://web.dev/learn/pwa/workbox)库。如果您习惯使用 Workbox,您可能更喜欢[Vite PWA 插件](https://vite-pwa-org.netlify.app/frameworks/sveltekit.html)。 ## 参考文献 关于服务工作者更一般的信息,我们推荐[MDN 网络文档](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers)。 # Server-only modules 像一位好朋友一样,SvelteKit 保护你的秘密。当你在同一个仓库中编写后端和前端时,很容易不小心将敏感数据导入到前端代码中(例如包含 API 密钥的环境变量)。SvelteKit 提供了一种完全防止这种情况的方法:仅服务器模块。 ## 私有环境变量 The [`$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 = [/* redacted */]; ``` ```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`)``这样的插值导入,但有一个小限制:在开发过程中,如果公共代码和仅服务器模块之间存在两个或更多动态导入,则在首次加载代码时,将无法检测到非法导入。 > \[!注意\] 单元测试框架如 Vitest 不区分服务器端代码和面向公众的代码。因此,当运行测试时,非法导入检测被禁用,由`process.env.TEST === 'true'`确定。 ## 进一步阅读 * [ 教程:环境变量](/tutorial/kit/env-static-private) # Snapshots 临时 DOM 状态——如侧边栏的滚动位置、``元素的内容等——在您从一个页面导航到另一个页面时会被丢弃。 例如,如果用户填写了表单但在提交前离开并返回,或者如果用户刷新了页面,他们填写的值将会丢失。在需要保留输入的情况下,您可以对 DOM 状态进行*快照*,这样在用户返回时可以恢复。 为此,从`snapshot`对象中导出具有`capture`和`restore`方法的`+page.svelte`或`+layout.svelte`: ```svelte