跳至主要内容

页面选项

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

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

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

prerender

至少您的应用程序的某些路由可以表示为在构建时生成的简单 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 的清单中排除,从而使您的服务器(或无服务器/边缘函数)更小。在某些情况下,您可能希望预渲染路由,但也将其包含在清单中(例如,对于 /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 中)。

具有 操作 的页面无法预渲染,因为服务器必须能够处理操作 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,但页面未被预渲染爬虫访问,因此未预渲染。

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

  • 确保 SvelteKit 可以通过遵循来自 config.kit.prerender.entriesentries 页面选项的链接来找到路由。如果通过爬取其他入口点未找到动态路由(即具有 [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 函数完成

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,则会改为渲染一个空的“shell”页面。如果您的页面无法在服务器上渲染(因为您使用了浏览器专用的全局变量,例如 document),这将很有用,但在大多数情况下不建议这样做(请参阅附录)。

+page
export const const ssr: falsessr = 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
export const const csr: falsecsr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!

禁用 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/ 的是 /x/y),并且 /x/x/ 被视为不同的 URL,这对 SEO 有害。

配置

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

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 对象在顶层合并(但不是更深层级)。这意味着如果您只想覆盖 +layout.js 中的一些值,则不需要在 +page.js 中重复所有值。例如,此布局配置...

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 } }

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

进一步阅读

在 GitHub 上编辑此页面

上一页 下一页