跳至主要內容

頁面選項

預設情況下,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,它將輸出適用於任何靜態網路伺服器的檔案。

預先渲染器將從您的應用程式的根目錄開始,並為找到的任何可預先渲染的頁面或 +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,則會改為渲染一個空的「外殼」頁面。如果您的頁面無法在伺服器上渲染(因為您使用了僅限瀏覽器的全域變數,例如 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,這反映了靜態網頁伺服器的慣例。

不建議忽略結尾斜線 — 因為在兩種情況下,相對路徑的語義有所不同(從 /x./y/y,但從 /x/./y/x/y),並且 /x/x/ 會被視為不同的 URL,這對 SEO 有害。

config

藉由轉接器的概念,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 } }

...這會導致該頁面的 config 值為 { runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }

延伸閱讀

在 GitHub 上編輯此頁面

上一頁 下一頁