SvelteKit 最新中文文档教程(5)—— 页面选项
<h2 id="前言">前言</h2><p>Svelte,一个语法简洁、入门容易,面向未来的前端框架。</p>
<p>从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,<strong>从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1</strong>:</p>
<p><img src="https://yayujs-blog.oss-cn-beijing.aliyuncs.com/405488775-48df16b1-939c-489b-8d52-6071869893f0.png"></p>
<p>Svelte 以其独特的编译时优化机制著称,具有<strong>轻量级</strong>、<strong>高性能</strong>、<strong>易上手</strong>等特性,<strong>非常适合构建轻量级 Web 项目</strong>。</p>
<p>为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。</p>
<p>如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p>
<h2 id="页面选项">页面选项</h2>
<p>默认情况下,SvelteKit 会首先在服务端上渲染(或预渲染)组件,并将其以 HTML 的形式发送到客户端。然后会在浏览器中再次渲染该组件以使其具有交互性,这个过程称为<strong>hydration</strong>。因此,您需要确保组件可以在这两个环境中都能运行。SvelteKit 随后会初始化一个<strong>路由器</strong>来接管后续的导航。</p>
<p>您可以通过在 <code>+page.js</code> 或 <code>+page.server.js</code> 中导出选项来对每个页面分别进行控制,或者通过共享的 <code>+layout.js</code> 或 <code>+layout.server.js</code> 来控制一组页面。要为整个应用定义一个选项,可以从根布局中导出它。子布局和页面会覆盖父布局中设置的值,因此——例如——您可以为整个应用启用预渲染,然后仅对需要动态渲染的页面禁用它。</p>
<p>您可以在应用的不同区域混合使用这些选项。例如,您可以为营销页面进行预渲染以获得最大的速度,为动态页面进行服务端渲染以兼顾 SEO 和可访问性,并通过只在客户端渲染的方式将管理后台部分变成一个 SPA。这让 SvelteKit 非常灵活多变。</p>
<h2 id="预渲染">预渲染</h2>
<p>您的应用中很可能至少有一些路由可以表示为在构建时生成的简单 HTML 文件。这些路由可以被<em>预渲染</em>。</p>
<pre><code class="language-js">/// file: +page.js/+page.server.js/+server.js
export const prerender = true;
</code></pre>
<p>或者,您也可以在根 <code>+layout.js</code> 或 <code>+layout.server.js</code> 中设置 <code>export const prerender = true</code> 来预渲染所有内容,并仅对显式标记为 <em>不可</em> 预渲染的页面进行排除:</p>
<pre><code class="language-js">/// file: +page.js/+page.server.js/+server.js
export const prerender = false;
</code></pre>
<p>带有 <code>prerender = true</code> 的路由将从用于动态 SSR 的清单中排除,从而使您的服务端(或 serverless/edge functions)的体积更小。有些情况下您可能想要预渲染一个路由,但仍然希望将其包含在清单中(例如,对像 <code>/blog/</code> 这样的路由,您可能想预渲染最新/最受欢迎的内容,但对数量庞大的内容长尾部分使用服务端渲染)——对于这些情况,您可以使用第三种选项 <code>'auto'</code>:</p>
<pre><code class="language-js">/// file: +page.js/+page.server.js/+server.js
export const prerender = 'auto';
</code></pre>
<blockquote>
<p>[!NOTE] 如果整个应用都适合预渲染,您可以使用 <code>adapter-static</code>,它会输出适用于任何静态 Web 服务器的文件。</p>
</blockquote>
<p>预渲染器会从应用的根目录开始,为它发现的所有可预渲染页面或 <code>+server.js</code> 路由生成文件。它会扫描每个页面中的指向其他可预渲染页面的 <code><a></code> 元素——因此通常情况下,您不需要指定应该访问哪些页面。如果<em>确实</em>需要指定预渲染器应该访问哪些页面,可以通过 <code>config.kit.prerender.entries</code> 或在您的动态路由中导出一个 <code>entries</code> 函数来实现。</p>
<p>在预渲染时,从 <code>$app/environment</code> 中导入的 <code>building</code> 值将为 <code>true</code>。</p>
<h3 id="预渲染服务端路由">预渲染服务端路由</h3>
<p>与其他页面选项不同,<code>prerender</code> 同样适用于 <code>+server.js</code> 文件。这些文件不受布局影响,但会继承从请求它们数据的页面(如果有的话)中设置的默认值。例如,如果 <code>+page.js</code> 包含以下 <code>load</code> 函数……</p>
<pre><code class="language-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();
}
</code></pre>
<p>……那么如果 <code>src/routes/my-server-route.json/+server.js</code> 没有包含它自己的 <code>export const prerender = false</code>,它将被视为可预渲染。</p>
<h3 id="何时不进行预渲染">何时不进行预渲染</h3>
<p>基本规则是:要使页面可预渲染,从服务端直接访问该页面时,无论是哪两个用户都应该得到相同的内容。</p>
<blockquote>
<p>[!NOTE] 并非所有页面都适合预渲染。任何被预渲染的内容都会被所有用户看到。当然,您可以在被预渲染的页面中通过 <code>onMount</code> 获取个性化数据,但这可能带来较差的用户体验,因为这会导致空白初始内容或加载指示器。</p>
</blockquote>
<p>请注意,您仍然可以对基于页面参数加载数据的页面(如 <code>src/routes/blog//+page.svelte</code>)进行预渲染。</p>
<p>在预渲染期间,禁止访问 <code>url.searchParams</code>。如果您需要使用它,请确保只在浏览器端使用(例如在 <code>onMount</code> 中)。</p>
<p>带有actions的页面无法进行预渲染,因为必须要有一个服务端处理该 action 的 <code>POST</code> 请求。</p>
<h3 id="路由冲突">路由冲突</h3>
<p>由于预渲染会写入文件系统,因此不可能同时生成目录和文件同名的两个端点。例如,<code>src/routes/foo/+server.js</code> 和 <code>src/routes/foo/bar/+server.js</code> 会尝试创建 <code>foo</code> 和 <code>foo/bar</code>,这是不可能的。</p>
<p>由于包含上述及其他原因,推荐始终使用文件扩展名——<code>src/routes/foo.json/+server.js</code> 和 <code>src/routes/foo/bar.json/+server.js</code> 将分别生成 <code>foo.json</code> 和 <code>foo/bar.json</code> 文件,并能和谐共存。</p>
<p>对于<em>页面</em>而言,我们通过写入 <code>foo/index.html</code> 而不是 <code>foo</code> 来规避此问题。</p>
<h3 id="故障排查">故障排查</h3>
<p>如果您遇到类似 “The following routes were marked as prerenderable, but were not prerendered” 的错误,则说明相关路由(或其父布局,如果它是某个页面)具有 <code>export const prerender = true</code>,但该页面未被预渲染爬虫访问,因此没有被预渲染。</p>
<p>由于这些路由无法进行动态服务端渲染,当用户尝试访问相关路由时会导致错误。可以通过以下几种方式修复:</p>
<ul>
<li>确保 SvelteKit 可以通过 <code>config.kit.prerender.entries</code> 或 <code>entries</code> 页面选项跟踪链接来找到这些路由。将动态路由(即带有 <code></code> 的页面)的链接添加到此选项中,如果它们无法通过爬取其他入口点找到,否则因为 SvelteKit 并不知道参数应取什么值,它们就不会被预渲染。未被标记为可预渲染的页面会被忽略,即使它们指向其他可预渲染页面,也不会被爬取。</li>
<li>确保 SvelteKit 可以在启用服务端渲染的其他预渲染页面中发现指向这些路由的链接。</li>
<li>将 <code>export const prerender = true</code> 更改为 <code>export const prerender = 'auto'</code>。带有 <code>'auto'</code> 的路由可以进行动态服务端渲染。</li>
</ul>
<h2 id="entries">entries</h2>
<p>SvelteKit 会通过将 <em>入口点</em> 作为起点并进行爬取,自动发现需要预渲染的页面。默认情况下,您的所有非动态路由都会被视作入口点——例如,如果您有如下路由……</p>
<pre><code class="language-bash">/ # 非动态
/blog # 非动态
/blog/# 动态,因为含有 ``
</code></pre>
<p>……SvelteKit 会预渲染 <code>/</code> 和 <code>/blog</code>,并在此过程中发现类似 <code><a href="/blog/hello-world"></code> 这样的链接,从而对新页面进行预渲染。</p>
<p>在大多数情况下,这就足够了。在某些情况下,可能并不存在指向 <code>/blog/hello-world</code> 页面(或其并非存在于已预渲染页面中)的链接,此时我们需要告诉 SvelteKit 它们的存在。</p>
<p>可以通过 <code>config.kit.prerender.entries</code> 完成,也可以在属于动态路由的 <code>+page.js</code>、<code>+page.server.js</code> 或 <code>+server.js</code> 中导出一个 <code>entries</code> 函数:</p>
<pre><code class="language-js">/// file: src/routes/blog//+page.server.js
/** @type {import('./$types').EntryGenerator} */
export function entries() {
return [{ slug: 'hello-world' }, { slug: 'another-blog-post' }];
}
export const prerender = true;
</code></pre>
<p><code>entries</code> 可以是一个 <code>async</code> 函数,这样您就如上例所示,从 CMS 或数据库检索文章列表。</p>
<h2 id="ssr">ssr</h2>
<p>通常,SvelteKit 会先在服务端上渲染您的页面,并将该 HTML 发送到客户端,在那里再进行水合。如果您将 <code>ssr</code> 设置为 <code>false</code>,它会改为渲染一个空的“外壳”页面。这在您的页面无法在服务端上渲染时(例如使用了只在浏览器可用的全局对象 <code>document</code>)会有用,但在大多数情况下并不推荐这样做(请参阅附录)。</p>
<pre><code class="language-js">/// file: +page.js
export const ssr = false;
// 如果 `ssr` 和 `csr` 都为 `false`,将不会渲染任何内容!
</code></pre>
<p>如果您在根 <code>+layout.js</code> 中添加 <code>export const ssr = false</code>,那么整个应用只会在客户端被渲染——这实际上意味着您将应用变成了一个 SPA。</p>
<h2 id="csr">csr</h2>
<p>通常,SvelteKit 会将服务端渲染的 HTML 水合 成交互式的客户端渲染 (CSR) 页面。有些页面根本不需要 JavaScript —— 很多博客文章或“关于”页面就是这种情况。对于这类页面,您可以禁用 CSR:</p>
<pre><code class="language-js">/// file: +page.js
export const csr = false;
// 如果 `csr` 和 `ssr` 都为 `false`,将不会渲染任何内容!
</code></pre>
<p>禁用 CSR 不会向客户端发送任何 JavaScript。这意味着:</p>
<ul>
<li>网页只能通过 HTML 和 CSS 来工作。</li>
<li>所有 Svelte 组件中的 <code><script></code> 标签将被移除。</li>
<li><code><form></code> 元素无法进行渐进式增强。</li>
<li>链接由浏览器通过全页面导航来处理。</li>
<li>将禁用热模块替换 (HMR)。</li>
</ul>
<p>您可以根据需要在开发环境中启用 <code>csr</code>(例如为了使用 HMR):</p>
<pre><code class="language-js">/// file: +page.js
import { dev } from '$app/environment';
export const csr = dev;
</code></pre>
<h2 id="trailingslash">trailingSlash</h2>
<p>默认情况下,SvelteKit 会移除 URL 中的尾部斜杠 —— 如果您访问 <code>/about/</code>,它会使用重定向到 <code>/about</code>。</p>
<p>您可以使用 <code>trailingSlash</code> 选项改变此行为,该选项可以是 <code>'never'</code>(默认)、<code>'always'</code> 或 <code>'ignore'</code>。</p>
<p>与其他页面选项一样,您可以在 <code>+layout.js</code> 或 <code>+layout.server.js</code> 中导出此值来对所有子页面生效。也可以从 <code>+server.js</code> 文件中导出此配置。</p>
<pre><code class="language-js">/// file: src/routes/+layout.js
export const trailingSlash = 'always';
</code></pre>
<p>此选项也会影响预渲染。如果 <code>trailingSlash</code> 为 <code>always</code>,像 <code>/about</code> 这样的路由会生成一个 <code>about/index.html</code> 文件;否则它会创建 <code>about.html</code>,遵循静态 Web 服务端的惯例。</p>
<blockquote>
<p>[!NOTE] 不推荐忽略尾部斜杠——两种情况下在相对路径的语义中是不同的(从 <code>/x</code> 的 <code>./y</code> 得到 <code>/y</code>,但从 <code>/x/</code> 的 <code>./y</code> 得到 <code>/x/y</code>),并且 <code>/x</code> 和 <code>/x/</code> 会被视为不同的 URL,这对 SEO 不利。</p>
</blockquote>
<h2 id="config">config</h2>
<p>凭借适配器的概念,SvelteKit 能够在多种平台上运行。这些平台中的每一个可能都有特定的配置来进一步调整部署——例如,在 Vercel 上,您可以选择将应用的部分部署到 edge 环境,其余部分则部署到 serverless 环境。</p>
<p><code>config</code> 是一个顶层具有键值对的对象。除此以外,其具体结构取决于您使用的适配器。每个适配器都应提供一个可导入的 <code>Config</code> 接口来实现类型安全。更多信息请参阅您所使用的适配器的文档。</p>
<pre><code class="language-js">// @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'
};
</code></pre>
<p><code>config</code> 对象会在顶层进行合并(但不会进行更深层级的合并)。这意味着如果您想在 <code>+page.js</code> 中只覆盖父级 <code>+layout.js</code> 中的某些设定,无需重复所有 <code>config</code> 值。例如,这里是布局中的配置……</p>
<pre><code class="language-js">/// file: src/routes/+layout.js
export const config = {
runtime: 'edge',
regions: 'all',
foo: {
bar: true
}
};
</code></pre>
<p>……以及这是页面中的配置……</p>
<pre><code class="language-js">/// file: src/routes/+page.js
export const config = {
regions: ['us1', 'us2'],
foo: {
baz: true
}
};
</code></pre>
<p>……对于该页面来说,最终的 <code>config</code> 值将合并为 <code>{ runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }</code>。</p>
<h2 id="进一步阅读">进一步阅读</h2>
<ul>
<li>教程:页面选项</li>
</ul>
<h2 id="svelte-中文文档">Svelte 中文文档</h2>
<p>点击查看中文文档 - SvelteKit 页面选项。</p>
<p>系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p><br><br>
来源:https://www.cnblogs.com/yayujs/p/18779923
頁:
[1]