動態

詳情 返回 返回

SvelteKit 最新中文文檔教程(5)—— 頁面選項 - 動態 詳情

前言

Svelte,一個語法簡潔、入門容易,面向未來的前端框架。

從 Svelte 誕生之初,就備受開發者的喜愛,根據統計,從 2019 年到 2024 年,連續 6 年一直是開發者最感興趣的前端框架 No.1

image.png

Svelte 以其獨特的編譯時優化機制著稱,具有輕量級高性能易上手等特性,非常適合構建輕量級 Web 項目

為了幫助大家學習 Svelte,我同時搭建了 Svelte 最新的中文文檔站點。

如果需要進階學習,也可以入手我的小冊《Svelte 開發指南》,語法篇、實戰篇、原理篇三大篇章帶你係統掌握 Svelte!

歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社羣”,踏上“前端大佬成長之路”。

頁面選項

默認情況下,SvelteKit 會首先在服務端上渲染(或預渲染)組件,並將其以 HTML 的形式發送到客户端。然後會在瀏覽器中再次渲染該組件以使其具有交互性,這個過程稱為hydration。因此,您需要確保組件可以在這兩個環境中都能運行。SvelteKit 隨後會初始化一個路由器來接管後續的導航。

您可以通過在 +page.js+page.server.js 中導出選項來對每個頁面分別進行控制,或者通過共享的 +layout.js+layout.server.js 來控制一組頁面。要為整個應用定義一個選項,可以從根佈局中導出它。子佈局和頁面會覆蓋父佈局中設置的值,因此——例如——您可以為整個應用啓用預渲染,然後僅對需要動態渲染的頁面禁用它。

您可以在應用的不同區域混合使用這些選項。例如,您可以為營銷頁面進行預渲染以獲得最大的速度,為動態頁面進行服務端渲染以兼顧 SEO 和可訪問性,並通過只在客户端渲染的方式將管理後台部分變成一個 SPA。這讓 SvelteKit 非常靈活多變。

預渲染

您的應用中很可能至少有一些路由可以表示為在構建時生成的簡單 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/edge functions)的體積更小。有些情況下您可能想要預渲染一個路由,但仍然希望將其包含在清單中(例如,對像 /blog/[slug] 這樣的路由,您可能想預渲染最新/最受歡迎的內容,但對數量龐大的內容長尾部分使用服務端渲染)——對於這些情況,您可以使用第三種選項 'auto'

/// file: +page.js/+page.server.js/+server.js
export const prerender = 'auto';
[!NOTE] 如果整個應用都適合預渲染,您可以使用 adapter-static,它會輸出適用於任何靜態 Web 服務器的文件。

預渲染器會從應用的根目錄開始,為它發現的所有可預渲染頁面或 +server.js 路由生成文件。它會掃描每個頁面中的指向其他可預渲染頁面的 <a> 元素——因此通常情況下,您不需要指定應該訪問哪些頁面。如果確實需要指定預渲染器應該訪問哪些頁面,可以通過 config.kit.prerender.entries 或在您的動態路由中導出一個 entries 函數來實現。

在預渲染時,從 $app/environment 中導入的 building 值將為 true

預渲染服務端路由

與其他頁面選項不同,prerender 同樣適用於 +server.js 文件。這些文件不受佈局影響,但會繼承從請求它們數據的頁面(如果有的話)中設置的默認值。例如,如果 +page.js 包含以下 load 函數……

/// 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 沒有包含它自己的 export const prerender = false,它將被視為可預渲染。

何時不進行預渲染

基本規則是:要使頁面可預渲染,從服務端直接訪問該頁面時,無論是哪兩個用户都應該得到相同的內容。

[!NOTE] 並非所有頁面都適合預渲染。任何被預渲染的內容都會被所有用户看到。當然,您可以在被預渲染的頁面中通過 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 函數:

/// 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)會有用,但在大多數情況下並不推薦這樣做(請參閲附錄)。

/// file: +page.js
export const ssr = false;
// 如果 `ssr` 和 `csr` 都為 `false`,將不會渲染任何內容!

如果您在根 +layout.js 中添加 export const ssr = false,那麼整個應用只會在客户端被渲染——這實際上意味着您將應用變成了一個 SPA。

csr

通常,SvelteKit 會將服務端渲染的 HTML 水合 成交互式的客户端渲染 (CSR) 頁面。有些頁面根本不需要 JavaScript —— 很多博客文章或“關於”頁面就是這種情況。對於這類頁面,您可以禁用 CSR:

/// file: +page.js
export const csr = false;
// 如果 `csr` 和 `ssr` 都為 `false`,將不會渲染任何內容!

禁用 CSR 不會向客户端發送任何 JavaScript。這意味着:

  • 網頁只能通過 HTML 和 CSS 來工作。
  • 所有 Svelte 組件中的 <script> 標籤將被移除。
  • <form> 元素無法進行漸進式增強。
  • 鏈接由瀏覽器通過全頁面導航來處理。
  • 將禁用熱模塊替換 (HMR)。

您可以根據需要在開發環境中啓用 csr(例如為了使用 HMR):

/// file: +page.js
import { dev } from '$app/environment';

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';

此選項也會影響預渲染。如果 trailingSlashalways,像 /about 這樣的路由會生成一個 about/index.html 文件;否則它會創建 about.html,遵循靜態 Web 服務端的慣例。

[!NOTE] 不推薦忽略尾部斜槓——兩種情況下在相對路徑的語義中是不同的(從 /x./y 得到 /y,但從 /x/./y 得到 /x/y),並且 /x/x/ 會被視為不同的 URL,這對 SEO 不利。

config

憑藉適配器的概念,SvelteKit 能夠在多種平台上運行。這些平台中的每一個可能都有特定的配置來進一步調整部署——例如,在 Vercel 上,您可以選擇將應用的部分部署到 edge 環境,其餘部分則部署到 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 對象會在頂層進行合併(但不會進行更深層級的合併)。這意味着如果您想在 +page.js 中只覆蓋父級 +layout.js 中的某些設定,無需重複所有 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
    }
};

……對於該頁面來説,最終的 config 值將合併為 { runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }

進一步閲讀

  • 教程:頁面選項

Svelte 中文文檔

點擊查看中文文檔 - SvelteKit 頁面選項。

系統學習 Svelte,歡迎入手小冊《Svelte 開發指南》。語法篇、實戰篇、原理篇三大篇章帶你係統掌握 Svelte!

此外我還寫過 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答讀者問等 14 個系列文章, 全系列文章目錄:https://github.com/mqyqingfeng/Blog

歡迎圍觀我的“網頁版朋友圈”、加入“冴羽·成長陪伴社羣”,踏上“前端大佬成長之路”。

user avatar heimatengyun 頭像
點贊 1 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.