Skip to main content

页面选项

默认情况下,SvelteKit 会首先在服务端上渲染(或预渲染)组件,并将其以 HTML 的形式发送到客户端。然后会在浏览器中再次渲染该组件以使其具有交互性,这个过程称为hydration。因此,您需要确保组件可以在这两个环境中都能运行。SvelteKit 随后会初始化一个路由器来接管后续的导航。

您可以通过在 +page.js+page.server.js 中导出选项来对每个页面分别进行控制,或者通过共享的 +layout.js+layout.server.js 来控制一组页面。要为整个应用定义一个选项,可以从根布局中导出它。子布局和页面会覆盖父布局中设置的值,因此——例如——您可以为整个应用启用预渲染,然后仅对需要动态渲染的页面禁用它。

您可以在应用的不同区域混合使用这些选项。例如,您可以为营销页面进行预渲染以获得最大的速度,为动态页面进行服务端渲染以兼顾 SEO 和可访问性,并通过只在客户端渲染的方式将管理后台部分变成一个 SPA。这让 SvelteKit 非常灵活多变。

预渲染

您的应用中很可能至少有一些路由可以表示为在构建时生成的简单 HTML 文件。这些路由可以被预渲染

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

或者,您也可以在根 +layout.js+layout.server.js 中设置 export const prerender = true 来预渲染所有内容,并仅对显式标记为 不可 预渲染的页面进行排除:

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

带有 prerender = true 的路由将从用于动态 SSR 的清单中排除,从而使您的服务端(或 serverless/edge functions)的体积更小。有些情况下您可能想要预渲染一个路由,但仍然希望将其包含在清单中(例如,对像 /blog/[slug] 这样的路由,您可能想预渲染最新/最受欢迎的内容,但对数量庞大的内容长尾部分使用服务端渲染)——对于这些情况,您可以使用第三种选项 'auto'

+page.js/+page.server.js/+server
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 函数……

+page
export const const prerender: trueprerender = true;

/** @type {import('./$types').PageLoad} */
export async function 
function load({ fetch }: {
    fetch: any;
}): Promise<any>
@type{import('./$types').PageLoad}
load
({ fetch: anyfetch }) {
const const res: anyres = await fetch: anyfetch('/my-server-route.json'); return await const res: anyres.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: trueprerender = true; export const const load: PageLoadload:
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: Responseres = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)fetch('/my-server-route.json'); return await const res: Responseres.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 中)。

带有actions的页面无法进行预渲染,因为必须要有一个服务端处理该 action 的 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 来规避此问题。

故障排查

如果您遇到类似 “The following routes were marked as prerenderable, but were not prerendered” 的错误,则说明相关路由(或其父布局,如果它是某个页面)具有 export const prerender = true,但该页面未被预渲染爬虫访问,因此没有被预渲染。

由于这些路由无法进行动态服务端渲染,当用户尝试访问相关路由时会导致错误。可以通过以下几种方式修复:

  • 确保 SvelteKit 可以通过 config.kit.prerender.entriesentries 页面选项跟踪链接来找到这些路由。将动态路由(即带有 [parameters] 的页面)的链接添加到此选项中,如果它们无法通过爬取其他入口点找到,否则因为 SvelteKit 并不知道参数应取什么值,它们就不会被预渲染。未被标记为可预渲染的页面会被忽略,即使它们指向其他可预渲染页面,也不会被爬取。
  • 确保 SvelteKit 可以在启用服务端渲染的其他预渲染页面中发现指向这些路由的链接。
  • export const prerender = true 更改为 export const prerender = 'auto'。带有 'auto' 的路由可以进行动态服务端渲染。

entries

SvelteKit 会通过将 入口点 作为起点并进行爬取,自动发现需要预渲染的页面。默认情况下,您的所有非动态路由都会被视作入口点——例如,如果您有如下路由……

/    # 非动态
/blog# 非动态
/blog/[slug]  # 动态,因为含有 `[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
/** @type {import('./$types').EntryGenerator} */
export function 
function entries(): {
    slug: string;
}[]
@type{import('./$types').EntryGenerator}
entries
() {
return [{ slug: stringslug: 'hello-world' }, { slug: stringslug: 'another-blog-post' }]; } export const const prerender: trueprerender = 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: EntryGeneratorentries:
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: stringslug: 'hello-world' }, { slug: stringslug: 'another-blog-post' }]; }; export const const prerender: trueprerender = true;

entries 可以是一个 async 函数,这样您就如上例所示,从 CMS 或数据库检索文章列表。

ssr

通常,SvelteKit 会先在服务端上渲染您的页面,并将该 HTML 发送到客户端,在那里再进行水合。如果您将 ssr 设置为 false,它会改为渲染一个空的“外壳”页面。这在您的页面无法在服务端上渲染时(例如使用了只在浏览器可用的全局对象 document)会有用,但在大多数情况下并不推荐这样做(请参阅附录)。

+page
export const const ssr: falsessr = false;
// 如果 `ssr` 和 `csr` 都为 `false`,将不会渲染任何内容!

如果您在根 +layout.js 中添加 export const ssr = false,那么整个应用只会在客户端被渲染——这实际上意味着您将应用变成了一个 SPA。

csr

通常,SvelteKit 会将服务端渲染的 HTML 水合 成交互式的客户端渲染 (CSR) 页面。有些页面根本不需要 JavaScript —— 很多博客文章或“关于”页面就是这种情况。对于这类页面,您可以禁用 CSR:

+page
export const const csr: falsecsr = false;
// 如果 `csr` 和 `ssr` 都为 `false`,将不会渲染任何内容!

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

  • 网页只能通过 HTML 和 CSS 来工作。
  • 所有 Svelte 组件中的 <script> 标签将被移除。
  • <form> 元素无法进行渐进式增强
  • 链接由浏览器通过全页面导航来处理。
  • 将禁用热模块替换 (HMR)。

您可以根据需要在开发环境中启用 csr(例如为了使用 HMR):

+page
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: booleancsr = 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 文件中导出此配置。

src/routes/+layout
export const const trailingSlash: "always"trailingSlash = 'always';

此选项也会影响预渲染。如果 trailingSlashalways,像 /about 这样的路由会生成一个 about/index.html 文件;否则它会创建 about.html,遵循静态 Web 服务端的惯例。

不推荐忽略尾部斜杠——两种情况下在相对路径的语义中是不同的(从 /x./y 得到 /y,但从 /x/./y 得到 /x/y),并且 /x/x/ 会被视为不同的 URL,这对 SEO 不利。

config

凭借适配器的概念,SvelteKit 能够在多种平台上运行。这些平台中的每一个可能都有特定的配置来进一步调整部署——例如,在 Vercel 上,您可以选择将应用的部分部署到 edge 环境,其余部分则部署到 serverless 环境。

config 是一个顶层具有键值对的对象。除此以外,其具体结构取决于您使用的适配器。每个适配器都应提供一个可导入的 Config 接口来实现类型安全。更多信息请参阅您所使用的适配器的文档。

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

export const const config: Configconfig: Config = {
  Config.runtime: stringruntime: 'edge'
};

config 对象会在顶层进行合并(但不会进行更深层级的合并)。这意味着如果您想在 +page.js 中只覆盖父级 +layout.js 中的某些设定,无需重复所有 config 值。例如,这里是布局中的配置……

src/routes/+layout
export const 
const config: {
    runtime: string;
    regions: string;
    foo: {
        bar: boolean;
    };
}
config
= {
runtime: stringruntime: 'edge', regions: stringregions: 'all',
foo: {
    bar: boolean;
}
foo
: {
bar: booleanbar: true } };

……以及这是页面中的配置……

src/routes/+page
export const 
const config: {
    regions: string[];
    foo: {
        baz: boolean;
    };
}
config
= {
regions: string[]regions: ['us1', 'us2'],
foo: {
    baz: boolean;
}
foo
: {
baz: booleanbaz: true } };

……对于该页面来说,最终的 config 值将合并为 { runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }

进一步阅读

在 GitHub 编辑此页面

上一页 下一页