页面选项
默认情况下,SvelteKit 会首先在服务器上渲染(或 预渲染)任何组件,并将其作为 HTML 发送到客户端。然后,它会在浏览器中再次渲染组件,使其在称为 水合 的过程中具有交互性。因此,您需要确保组件可以在这两个地方运行。然后,SvelteKit 将初始化一个 路由器,接管后续的导航。
您可以通过从 +page.js
或 +page.server.js
导出选项,或使用共享的 +layout.js
或 +layout.server.js
为页面组控制每个选项。要为整个应用程序定义选项,请从根布局导出它。子布局和页面会覆盖父布局中设置的值,因此,例如,您可以为整个应用程序启用预渲染,然后将其禁用对需要动态渲染的页面。
您可以在应用程序的不同区域混合和匹配这些选项。例如,您可以预渲染您的营销页面以获得最大速度,为您的动态页面服务器端渲染以实现 SEO 和可访问性,并将您的管理部分变成 SPA,仅在客户端渲染它。这使得 SvelteKit 非常通用。
prerender
至少您的应用程序的某些路由可以表示为在构建时生成的简单 HTML 文件。这些路由可以 预渲染。
export const const prerender: true
prerender = true;
或者,您可以在根 +layout.js
或 +layout.server.js
中设置 export const prerender = true
并预渲染除明确标记为不可预渲染的页面之外的所有页面
export const const prerender: false
prerender = false;
具有 prerender = true
的路由将从用于动态 SSR 的清单中排除,从而使您的服务器(或无服务器/边缘函数)更小。在某些情况下,您可能希望预渲染路由,但也将其包含在清单中(例如,对于 /blog/[slug]
之类的路由,您希望预渲染最新/最受欢迎的内容,但服务器渲染长尾)——对于这些情况,还有第三个选项,“auto”
export const const prerender: "auto"
prerender = 'auto';
如果您的整个应用程序适合预渲染,您可以使用
adapter-static
,它将输出适合与任何静态 Web 服务器一起使用的文件。
预渲染器将从应用程序的根目录开始,并为它找到的任何可预渲染页面或 +server.js
路由生成文件。每个页面都会扫描指向其他作为预渲染候选者的页面的 <a>
元素——因此,您通常不需要指定应访问哪些页面。如果您确实需要指定预渲染器应访问哪些页面,您可以使用 config.kit.prerender.entries
或通过从您的动态路由导出 entries
函数来实现。
在预渲染期间,从 $app/environment
导入的 building
的值将为 true
。
预渲染服务器路由
与其他页面选项不同,prerender
也适用于 +server.js
文件。这些文件不受布局的影响,但会继承从获取数据的页面继承默认值(如果有)。例如,如果 +page.js
包含此 load
函数...
export const const prerender: true
prerender = true;
/** @type {import('./$types').PageLoad} */
export async function function load({ fetch }: {
fetch: any;
}): Promise<any>
load({ fetch: any
fetch }) {
const const res: any
res = await fetch: any
fetch('/my-server-route.json');
return await const res: any
res.json();
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const prerender: true
prerender = true;
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch }) => {
const const res: Response
res = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch('/my-server-route.json');
return await const res: Response
res.Body.json(): Promise<any>
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.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
或entries
页面选项的链接来找到路由。如果通过爬取其他入口点未找到动态路由(即具有[parameters]
的页面)的链接,则将这些链接添加到此选项中,否则它们不会被预渲染,因为 SvelteKit 不知道参数应该是什么值。未标记为可预渲染的页面将被忽略,并且它们到其他页面的链接也不会被爬取,即使其中一些是可预渲染的。 - 确保 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
函数完成
/** @type {import('./$types').EntryGenerator} */
export function function entries(): {
slug: string;
}[]
entries() {
return [
{ slug: string
slug: 'hello-world' },
{ slug: string
slug: 'another-blog-post' }
];
}
export const const prerender: true
prerender = true;
import type { type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
EntryGenerator } from './$types';
export const const entries: EntryGenerator
entries: type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
EntryGenerator = () => {
return [
{ slug: string
slug: 'hello-world' },
{ slug: string
slug: 'another-blog-post' }
];
};
export const const prerender: true
prerender = true;
entries
可以是 async
函数,允许您(例如)从 CMS 或数据库中检索帖子列表,如上例所示。
ssr
通常,SvelteKit 会首先在服务器上渲染您的页面,并将该 HTML 发送到客户端,在客户端进行 水合。如果将 ssr
设置为 false
,则会改为渲染一个空的“shell”页面。如果您的页面无法在服务器上渲染(因为您使用了浏览器专用的全局变量,例如 document
),这将很有用,但在大多数情况下不建议这样做(请参阅附录)。
export const const ssr: false
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
export const const csr: false
csr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!
禁用 CSR 不会将任何 JavaScript 发送到客户端。这意味着
- 网页应该只使用 HTML 和 CSS 才能工作。
- 所有 Svelte 组件内部的
<script>
标签都会被移除。 <form>
元素无法 渐进增强。- 链接由浏览器使用完整页面导航处理。
- 热模块替换 (HMR) 将被禁用。
您可以在开发过程中启用 csr
(例如,利用 HMR)如下所示
import { const dev: boolean
Whether the dev server is running. This is not guaranteed to correspond to NODE_ENV
or MODE
.
dev } from '$app/environment';
export const const csr: boolean
csr = const dev: boolean
Whether the dev server is running. This is not guaranteed to correspond to NODE_ENV
or MODE
.
dev;
trailingSlash
默认情况下,SvelteKit 会从 URL 中删除尾部斜杠——如果您访问 /about/
,它将响应重定向到 /about
。您可以使用 trailingSlash
选项更改此行为,该选项可以是 'never'
(默认值)、'always'
或 'ignore'
。
与其他页面选项一样,您可以从 +layout.js
或 +layout.server.js
导出此值,它将应用于所有子页面。您还可以从 +server.js
文件导出配置。
export const const trailingSlash: "always"
trailingSlash = 'always';
此选项还会影响预渲染。如果 trailingSlash
为 always
,则类似 /about
的路由将生成 about/index.html
文件,否则将生成 about.html
文件,这与静态 Web 服务器约定相符。
不建议忽略尾部斜杠——这两种情况下的相对路径语义不同(来自
/x
的./y
是/y
,但来自/x/
的是/x/y
),并且/x
和/x/
被视为不同的 URL,这对 SEO 有害。
配置
借助适配器的概念,SvelteKit 能够在各种平台上运行。这些平台中的每一个都可能具有特定的配置来进一步调整部署——例如,在 Vercel 上,您可以选择将应用程序的某些部分部署到边缘,而其他部分部署到无服务器环境。
config
是一个在顶层具有键值对的对象。除此之外,具体的形状取决于您使用的适配器。每个适配器都应该提供一个 Config
接口来导入以确保类型安全。请参阅适配器的文档以获取更多信息。
/** @type {import('some-adapter').Config} */
export const const config: Config
config = {
Config.runtime: string
runtime: 'edge'
};
import type { Config } from 'some-adapter';
export const const config: Config
config: Config = {
Config.runtime: string
runtime: 'edge'
};
config
对象在顶层合并(但不是更深层级)。这意味着如果您只想覆盖 +layout.js
中的一些值,则不需要在 +page.js
中重复所有值。例如,此布局配置...
export const const config: {
runtime: string;
regions: string;
foo: {
bar: boolean;
};
}
config = {
runtime: string
runtime: 'edge',
regions: string
regions: 'all',
foo: {
bar: boolean;
}
foo: {
bar: boolean
bar: true
}
}
...被此页面配置覆盖...
export const const config: {
regions: string[];
foo: {
baz: boolean;
};
}
config = {
regions: string[]
regions: ['us1', 'us2'],
foo: {
baz: boolean;
}
foo: {
baz: boolean
baz: true
}
}
...这导致该页面的配置值为 { runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }
。