跳至主要内容

核心概念

页面选项

在 GitHub 上编辑此页面

默认情况下,SvelteKit 将首先在服务器上呈现(或预呈现)任何组件,并将其作为 HTML 发送到客户端。然后,它将在浏览器中再次呈现该组件,以使其在称为水化的过程中具有交互性。因此,你需要确保组件可以在两个地方运行。然后,SvelteKit 将初始化一个路由器,该路由器将接管后续导航。

你可以通过从+page.js+page.server.js导出选项,或使用共享的+layout.js+layout.server.js为组页面来逐页控制这些选项。要为整个应用定义一个选项,请从根布局中导出它。子布局和页面会覆盖父布局中设置的值,因此,例如,你可以为整个应用启用预呈现,然后为需要动态呈现的页面禁用它。

你可以在应用的不同区域混合和匹配这些选项。例如,你可以预呈现你的营销页面以获得最快的速度,为 SEO 和可访问性服务器呈现你的动态页面,并通过仅在客户端呈现你的管理部分将其变成 SPA。这使得 SvelteKit 非常通用。

预呈现

你的应用至少有一些路由很可能可以表示为在构建时生成的简单 HTML 文件。这些路由可以预呈现

+page.js/+page.server.js/+server.js
ts
export const prerender = true;
+page.ts/+page.server.js/+server.js
ts
export const prerender = true;

或者,你可以在根目录的 +layout.js+layout.server.js 中设置 export const prerender = true,并预渲染除明确标记为不可预渲染的页面之外的所有内容

+page.js/+page.server.js/+server.js
ts
export const prerender = false;
+page.ts/+page.server.js/+server.js
ts
export const prerender = false;

具有 prerender = true 的路由将从用于动态 SSR 的清单中排除,从而使你的服务器(或无服务器/边缘函数)更小。在某些情况下,你可能希望预渲染一个路由,但也要将其包含在清单中(例如,对于像 /blog/[slug] 这样的路由,你希望预渲染最新/最受欢迎的内容,但服务器渲染长尾)——对于这些情况,有一个第三个选项,“auto”

+page.js/+page.server.js/+server.js
ts
export const prerender = 'auto';
+page.ts/+page.server.js/+server.js
ts
export const prerender = 'auto';

如果你的整个应用程序适合预渲染,你可以使用 adapter-static,它将输出适合与任何静态 Web 服务器一起使用的文件。

预渲染器将从应用程序的根目录开始,并为它找到的任何可预渲染页面或 +server.js 路由生成文件。每个页面都会被扫描,以查找指向其他适合预渲染的页面的 <a> 元素——因此,你通常不需要指定应该访问哪些页面。如果你确实需要指定哪些页面应该被预渲染器访问,你可以使用 config.kit.prerender.entries 来指定,或者通过从你的动态路由导出一个 entries 函数来指定。

在预渲染时,从 $app/environment 导入的 building 的值将为 true

预渲染服务器路由

与其他页面选项不同,prerender 也适用于 +server.js 文件。这些文件不受布局的影响,但会继承从它们获取数据的页面(如果有)的默认值。例如,如果一个 +page.js 包含这个 load 函数...

+page.js
ts
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();
}
+page.ts
ts
import type { PageLoad } from './$types';
export const prerender = true;
export const load: PageLoad = async ({ 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 是禁止的。如果您需要使用它,请确保您只在浏览器中执行此操作(例如在 onMount 中)。

带有 操作 的页面无法预渲染,因为服务器必须能够处理操作 POST 请求。

路由冲突

由于预渲染会写入文件系统,因此不可能有两个端点导致目录和文件具有相同名称。例如,src/routes/foo/+server.jssrc/routes/foo/bar/+server.js 将尝试创建 foofoo/bar,这是不可能的。

出于此原因和其他原因,建议您始终包含文件扩展名——src/routes/foo.json/+server.jssrc/routes/foo/bar.json/+server.js 将导致 foo.jsonfoo/bar.json 文件和谐地并排存在。

对于页面,我们通过编写 foo/index.html 而不是 foo 来绕过这个问题。

故障排除

如果您遇到类似“以下路由被标记为可预渲染,但未预渲染”的错误,那是因为相关路由(或父布局,如果是页面)具有 export const prerender = true,但页面实际上未预渲染,因为预渲染爬虫未到达它。

由于这些路由无法动态地服务器渲染,因此当人们尝试访问相关路由时,这将导致错误。有两种方法可以解决此问题

  • 通过关注 config.kit.prerender.entriesentries 页面选项中的链接,确保 SvelteKit 可以找到路由。如果未通过爬取其他入口点找到动态路由(即带有 [parameters] 的页面),请将指向这些路由的链接添加到此选项,否则它们不会被预渲染,因为 SvelteKit 不知道参数应该具有什么值。未标记为可预渲染的页面将被忽略,并且指向其他页面的链接不会被爬取,即使其中一些页面是可预渲染的。
  • export const prerender = true 更改为 export const prerender = 'auto'。具有 'auto' 的路由可以动态地在服务器上呈现

entries

SvelteKit 将从入口点开始自动发现要预渲染的页面并对其进行抓取。默认情况下,所有非动态路由都被视为入口点——例如,如果你有以下路由...

/             # non-dynamic
/blog         # non-dynamic
/blog/[slug]  # dynamic, because of `[slug]`

...SvelteKit 将预渲染 //blog,并在该过程中发现诸如 <a href="/blog/hello-world"> 之类的链接,从而为其提供新的页面进行预渲染。

大多数情况下,这已经足够了。在某些情况下,可能不存在指向诸如 /blog/hello-world 之类的页面的链接(或可能不存在于预渲染页面中),在这种情况下,我们需要告诉 SvelteKit 这些页面的存在。

这可以通过 config.kit.prerender.entries 来完成,或者通过从属于动态路由的 +page.js+page.server.js+server.js 导出一个 entries 函数来完成

src/routes/blog/[slug]/+page.server.js
ts
/** @type {import('./$types').EntryGenerator} */
export function entries() {
return [
{ slug: 'hello-world' },
{ slug: 'another-blog-post' }
];
}
export const prerender = true;
src/routes/blog/[slug]/+page.server.ts
ts
import type { EntryGenerator } from './$types';
export const entries: EntryGenerator = () => {
return [{ slug: 'hello-world' }, { slug: 'another-blog-post' }];
};
export const prerender = true;

entries 可以是一个 async 函数,它允许你(例如)从 CMS 或数据库中检索文章列表,如上面的示例所示。

ssr

通常,SvelteKit 首先在服务器上呈现你的页面,并将该 HTML 发送到客户端,客户端会对其进行 水合。如果你将 ssr 设置为 false,它将呈现一个空的“外壳”页面。如果你无法在服务器上呈现你的页面(例如,你使用了仅限浏览器的全局变量,如 document),这将很有用,但在大多数情况下不建议这样做(请参阅附录)。

+page.js
ts
export const ssr = false;
// If both `ssr` and `csr` are `false`, nothing will be rendered!
+page.ts
ts
export const ssr = false;
// If both `ssr` and `csr` are `false`, nothing will be rendered!

如果你将 export const ssr = false 添加到根 +layout.js,你的整个应用程序将仅在客户端呈现——这实际上意味着你将你的应用程序变成了 SPA。

csr

通常,SvelteKit 会将你的服务器呈现的 HTML 水合 为一个交互式的客户端呈现 (CSR) 页面。有些页面根本不需要 JavaScript——许多博客文章和“关于”页面属于这一类。在这些情况下,你可以禁用 CSR

+page.js
ts
export const csr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!
+page.ts
ts
export const csr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!

禁用 CSR 不会向客户端发送任何 JavaScript。这意味着

  • 该网页应该仅使用 HTML 和 CSS 工作。
  • 所有 Svelte 组件内的 <script> 标签都会被移除。
  • <form> 元素无法 渐进增强
  • 链接由浏览器通过全页面导航进行处理。

trailingSlash

默认情况下,SvelteKit 将从 URL 中移除尾部斜杠——如果你访问 /about/,它将响应重定向到 /about。你可以使用 trailingSlash 选项更改此行为,它可以是 'never'(默认值)、'always''ignore'

与其他页面选项一样,你可以从 +layout.js+layout.server.js 导出此值,它将应用于所有子页面。你还可以从 +server.js 文件导出配置。

src/routes/+layout.js
ts
export const trailingSlash = 'always';
src/routes/+layout.ts
ts
export const trailingSlash = 'always';

此选项还会影响 预渲染。如果 trailingSlashalways,则类似于 /about 的路由将生成一个 about/index.html 文件,否则它将创建 about.html,反映静态 Web 服务器约定。

不建议忽略尾部斜杠——两种情况下相对路径的语义不同(从 /x 中的 ./y/y,但从 /x/ 中为 /x/y),并且 /x/x/ 被视为单独的 URL,这对 SEO 有害。

config

凭借 适配器 的概念,SvelteKit 能够在各种平台上运行。其中每一个可能都有特定的配置来进一步调整部署——例如,在 Vercel 上,你可以选择将应用程序的某些部分部署在边缘,而其他部分部署在无服务器环境中。

config 是一个在顶层具有键值对的对象。除此之外,具体形状取决于你使用的适配器。每个适配器都应该提供一个 Config 接口以导入类型安全。有关更多信息,请查阅适配器的文档。

src/routes/+page.js
ts
/** @type {import('some-adapter').Config} */
export const config = {
runtime: 'edge'
};
src/routes/+page.ts
ts
import type { Config } from 'some-adapter';
export const config: Config = {
runtime: 'edge',
};

config 对象在顶层合并(但在更深层次)。这意味着如果你只想覆盖上层 +layout.js 中的一些值,则无需在 +page.js 中重复所有值。例如,此布局配置...

src/routes/+layout.js
ts
export const config = {
runtime: 'edge',
regions: 'all',
foo: {
bar: true
}
}
src/routes/+layout.ts
ts
export const config = {
runtime: 'edge',
regions: 'all',
foo: {
bar: true,
},
};

...被此页面配置覆盖...

src/routes/+page.js
ts
export const config = {
regions: ['us1', 'us2'],
foo: {
baz: true
}
}
src/routes/+page.ts
ts
export const config = {
regions: ['us1', 'us2'],
foo: {
baz: true,
},
};

...这会导致该页面的配置值{ runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }

进一步阅读

上一个 表单操作
下一个 状态管理