默认情况下,SvelteKit 首先在服务器渲染或预渲染组件,将 HTML 发送给客户端,再在浏览器中通过水合(hydration)让组件具备交互能力。因此,组件必须能在服务器和浏览器两处运行。之后,SvelteKit 初始化路由器,接管后续导航。
可以从 +page.js 或 +page.server.js 导出选项,逐页控制这些行为;也可以从共享的 +layout.js、+layout.server.js 控制一组页面。根布局中的选项适用于整个应用。子布局和页面会覆盖父布局的值,因此可以先为全站启用预渲染,再为需要动态渲染的页面关闭它。
应用的不同区域可以组合使用不同策略。例如,营销页面预渲染以提高速度,动态页面服务端渲染以改善 SEO 和无障碍访问,管理后台只在客户端渲染为 SPA。
prerender
应用中至少一部分路由可能可以表示为构建时生成的简单 HTML 文件,这类路由可以预渲染:
/// file: +page.js/+page.server.js/+server.js
export const prerender = true;
也可以在根 +layout.js 或 +layout.server.js 设置 export const prerender = true,再为不能预渲染的页面明确关闭:
/// file: +page.js/+page.server.js/+server.js
export const prerender = false;
prerender = true 的路由不会进入动态 SSR 的清单,因而可以缩小服务器或 serverless/边缘函数的体积。如果既要预渲染,又要保留动态渲染能力,例如 /blog/[slug] 预生成热门文章、服务端渲染长尾内容,可使用第三个值 'auto':
/// file: +page.js/+page.server.js/+server.js
export const prerender = 'auto';
整个应用都适合预渲染时,可以使用 adapter-static,输出可部署到任意静态 Web 服务器的文件。
预渲染器从应用根目录开始,为发现的可预渲染页面或 +server.js 路由生成文件,并扫描页面中的 <a>,继续发现其他候选页面。因此通常不必逐个列出访问目标。需要明确指定时,可配置 config.prerender.entries,或从动态路由导出 entries 函数。
预渲染期间,从 $app/env 导入的 building 为 true。
预渲染服务端路由
与其他页面选项不同,prerender 也适用于 +server.js。这些文件不受布局影响,但会从请求其数据的页面继承默认值。例如 +page.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 如果没有自行导出 prerender = false,也会被当作可预渲染路由。
哪些场景不应预渲染
基本规则是:任意两个直接访问该页面的用户,都必须从服务器得到相同内容。
并非所有页面都适合预渲染。预渲染内容会对所有用户可见。可以在预渲染页面的
onMount中获取个性化数据,但初始空白或加载提示可能降低体验。
根据页面参数加载数据的页面仍可预渲染,例如 src/routes/blog/[slug]/+page.svelte。
预渲染时禁止访问 url.searchParams。需要它时,应确保只在浏览器中访问,例如放在 onMount 中。
带有 actions 的页面不能预渲染,因为服务器必须处理 action 的 POST 请求。
路由冲突
预渲染会写入文件系统,不能让两个端点导致目录和文件同名。例如 src/routes/foo/+server.js 和 src/routes/foo/bar/+server.js 分别需要创建 foo 文件及 foo/bar,无法共存。
因此建议为端点包含扩展名。例如 src/routes/foo.json/+server.js 和 src/routes/foo/bar.json/+server.js 会生成 foo.json、foo/bar.json,可以共存。页面通过写入 foo/index.html 而不是 foo 来避免这个问题。
排查错误
遇到 “The following routes were marked as prerenderable, but were not prerendered” 时,表示该路由或页面的父布局设置了 prerender = true,但预渲染爬虫没有访问到它。
这些路由又不能动态服务端渲染,因此访问会出错。可按以下方式处理:
- 通过
config.prerender.entries或页面的entries让预渲染器发现路由。若带[parameters]的动态路由不能从其他入口发现,就显式添加;否则 SvelteKit 不知道参数应取什么值。未标记可预渲染的页面会被忽略,其链接也不会被遍历,即使链接目标可以预渲染。 - 从其他已经预渲染且启用服务端渲染的页面链接到目标路由。
- 将
prerender = true改为prerender = 'auto',允许动态服务端渲染。
entries
SvelteKit 从入口开始爬取并自动发现页面。默认所有非动态路由都是入口。例如:
/ # non-dynamic
/blog # non-dynamic
/blog/[slug] # dynamic, because of `[slug]`
它会预渲染 / 和 /blog,在过程中发现 <a href="/blog/hello-world"> 这样的链接,继续预渲染新页面。多数情况这已足够。但若此类链接不存在,或只存在于未预渲染页面,就必须显式告知这些页面的存在。
可以配置 config.prerender.entries,或从动态路由所属的 +page.js、+page.server.js、+server.js 导出 entries:
/// 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 发给客户端水合;预渲染要保存完整内容也需要这一过程。设置 ssr = false 后,服务器只生成空壳页面。如果页面使用 document 等浏览器专属全局对象而无法在服务器运行,这可能有用,但通常不建议关闭 SSR,参见 SSR 说明。
/// file: +page.js
export const ssr = false;
// If both `ssr` and `csr` are `false`, nothing will be rendered!
根 +layout.js 设置 ssr = false 会使整个应用只在客户端渲染,实质上成为 SPA。若目标是静态生成网站,不应这样做。
页面选项全部是布尔值或字符串字面量时,SvelteKit 静态求值;否则会在服务器导入
+page.js或+layout.js以求值,包括构建时,以及应用不是完全静态时的运行阶段。后一种情况不能在模块加载时运行浏览器专属代码,实际应把相关导入放在+page.svelte或+layout.svelte中。
csr
通常 SvelteKit 将服务端 HTML 水合为可交互的客户端渲染页面。有些博客文章或“关于”页面完全不需要 JavaScript,可以关闭 CSR:
/// file: +page.js
export const csr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!
关闭 CSR 后,不向客户端发送 JavaScript,因此:
- 页面必须仅靠 HTML 和 CSS 工作。
- 所有 Svelte 组件内的
<script>标签被移除。 <form>不能渐进增强。- 链接由浏览器整页导航处理。
- 热模块替换(HMR)关闭。
可以在开发时启用 CSR,以使用 HMR:
/// file: +page.js
import { dev } from '$app/env';
export const csr = dev;
trailingSlash
默认 SvelteKit 移除 URL 尾部斜杠:访问 /about/ 会重定向到 /about。trailingSlash 可设为 'never'(默认)、'always' 或 'ignore'。
与其他页面选项一样,可以从 +layout.js、+layout.server.js 导出并作用于子页面,也可以从 +server.js 导出:
/// file: src/routes/+layout.js
export const trailingSlash = 'always';
它也影响预渲染。always 会把 /about 生成到 about/index.html,否则生成 about.html,符合静态服务器惯例。
不建议忽略尾部斜杠:相对路径的含义不同,
/x中的./y是/y,/x/中则是/x/y;而且/x与/x/被当作不同 URL,不利于 SEO。
config
SvelteKit 通过适配器运行在不同平台,每个平台可能有特定部署设置。例如 Vercel 可以让应用的部分区域运行在边缘环境,其他区域运行在 serverless 环境。
config 顶层是键值对象,具体结构由适配器决定。每个适配器应提供可导入的 Config 接口来保障类型安全,具体参见相应适配器文档。
// @filename: ambient.d.ts
declare module 'some-adapter' {
export interface Config { runtime: string }
}
// @filename: index.js
// ---cut---
/// file: src/routes/+page.js
/** @type {import('some-adapter').Config} */
export const config = {
runtime: 'edge'
};
config 只在顶层合并,不进行深层合并。页面只需提供想覆盖的布局值。例如布局:
/// file: src/routes/+layout.js
export const config = {
runtime: 'edge',
regions: 'all',
foo: {
bar: true
}
}
被以下页面配置覆盖:
/// file: src/routes/+page.js
export const config = {
regions: ['us1', 'us2'],
foo: {
baz: true
}
}
最终页面配置为 { runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }。
延伸阅读
来源:Page options,SvelteKit 官方文档。本文为中文翻译,保留全部选项、示例及限制条件,采用原页当前的 $app/env 模块名称。
Copyright (c) 2020 SvelteKit 贡献者。采用 MIT 许可。特此免费授予任何取得本软件及相关文档副本的人不受限制地处理本软件的权利,包括使用、复制、修改、合并、发布、分发、再许可和/或出售副本,并允许获得本软件的人这样做,但须在所有副本或实质部分中保留上述版权声明和本许可声明。
本软件按原样提供,不作任何明示或默示保证,包括但不限于适销性、特定用途适用性和不侵权。无论依据合同、侵权或其他理由,作者或版权持有人均不对因本软件、使用本软件或其他相关交易而产生的索赔、损害或其他责任负责。











暂无评论内容