跳到主要內容

核心概念

路由

在 GitHub 上編輯此頁面

SvelteKit 的核心是一個基於檔案系統的路由器。應用程式的路由(即使用者可以存取的 URL 路徑)是由程式碼庫中的目錄定義的

  • src/routes 是根路由
  • src/routes/about 建立一個 /about 路由
  • src/routes/blog/[slug] 建立一個具有參數 slug 的路由,當使用者要求類似 /blog/hello-world 的頁面時,可以使用該參數動態載入資料

你可以透過編輯 專案設定src/routes 變更為不同的目錄。

每個路由目錄包含一個或多個路由檔案,它們可以用其 + 前綴來識別。

+page

+page.svelte

+page.svelte 元件定義了應用程式的頁面。預設情況下,頁面會在伺服器上(SSR)渲染以進行初始要求,並在瀏覽器中(CSR)渲染以進行後續導覽。

src/routes/+page.svelte
<h1>Hello and welcome to my site!</h1>
<a href="/about">About my site</a>
src/routes/about/+page.svelte
<h1>About this site</h1>
<p>TODO...</p>
<a href="/">Home</a>
src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

<h1>{data.title}</h1>
<div>{@html data.content}</div>

請注意,SvelteKit 使用 <a> 元素在路由之間導覽,而不是特定於架構的 <Link> 元件。

+page.js

通常,頁面需要在渲染之前載入一些資料。為此,我們加入一個 +page.js 模組,該模組會匯出一個 load 函式

src/routes/blog/[slug]/+page.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...'
};
}
error(404, 'Not found');
}
src/routes/blog/[slug]/+page.ts
ts
import { error } from '@sveltejs/kit';
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
if (params.slug === 'hello-world') {
return {
title: 'Hello world!',
content: 'Welcome to our blog. Lorem ipsum dolor sit amet...',
};
}
error(404, 'Not found');
};

此函式與 +page.svelte 一起執行,這表示它會在伺服器端渲染期間在伺服器上執行,並在客戶端導覽期間在瀏覽器中執行。請參閱 load 以取得 API 的完整詳細資料。

除了 load 之外,+page.js 還可以匯出設定頁面行為的值

  • export const prerender = truefalse'auto'
  • export const ssr = truefalse
  • export const csr = truefalse

您可以在 頁面選項 中找到有關這些選項的更多資訊。

+page.server.js

如果您的 load 函式只能在伺服器上執行,例如,如果它需要從資料庫擷取資料,或者您需要存取私人 環境變數,例如 API 金鑰,則可以將 +page.js 重新命名為 +page.server.js,並將 PageLoad 類型變更為 PageServerLoad

src/routes/blog/[slug]/+page.server.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
const post = await getPostFromDatabase(params.slug);
if (post) {
return post;
}
error(404, 'Not found');
}
src/routes/blog/[slug]/+page.server.ts
ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params }) => {
const post = await getPostFromDatabase(params.slug);
if (post) {
return post;
}
error(404, 'Not found');
};

在客戶端導覽期間,SvelteKit 會從伺服器載入這些資料,這表示傳回的值必須使用 devalue 序列化。請參閱 load 以取得 API 的完整詳細資料。

+page.js 類似,+page.server.js 可以匯出 頁面選項,例如 prerenderssrcsr

+page.server.js 檔案也可以匯出動作。如果 load 讓您從伺服器讀取資料,actions 讓您可以使用 <form> 元素將資料寫入伺服器。若要了解如何使用它們,請參閱 表單動作 部分。

+error

如果在 load 期間發生錯誤,SvelteKit 會呈現預設的錯誤頁面。您可以透過新增 +error.svelte 檔案,針對每個路由自訂此錯誤頁面

src/routes/blog/[slug]/+error.svelte
<script>
	import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>
src/routes/blog/[slug]/+error.svelte
<script lang="ts">
	import { page } from '$app/stores';
</script>

<h1>{$page.status}: {$page.error.message}</h1>

SvelteKit 會「向上尋找」最近的錯誤邊界,如果上述檔案不存在,它會嘗試 src/routes/blog/+error.svelte,然後嘗試 src/routes/+error.svelte,最後才會呈現預設的錯誤頁面。如果這樣失敗了(或如果錯誤是由根 +layoutload 函式引發,而該函式位於根 +error 的「上方」),SvelteKit 會放棄並呈現靜態後備錯誤頁面,您可以透過建立 src/error.html 檔案自訂此頁面。

如果錯誤發生在 +layout(.server).js 中的 load 函式內,樹狀結構中最近的錯誤邊界會是該佈局上方+error.svelte 檔案(而不是其旁邊)。

如果找不到路由(404),將會使用 src/routes/+error.svelte(或如果該檔案不存在,則使用預設錯誤頁面)。

當錯誤發生在 handle+server.js 要求處理程式內時,不會使用 +error.svelte

你可以 在此處 閱讀更多關於錯誤處理的資訊。

+layout

到目前為止,我們將頁面視為完全獨立的元件,在導覽時,現有的 +page.svelte 元件將會被銷毀,新的元件將取而代之。

但在許多應用程式中,有些元素應該在每個頁面上都可見,例如頂層導覽或頁尾。我們可以將它們放在佈局中,而不是在每個 +page.svelte 中重複它們。

+layout.svelte

若要建立套用至每個頁面的佈局,請建立一個名為 src/routes/+layout.svelte 的檔案。預設佈局(如果你沒有自訂佈局,SvelteKit 會使用的佈局)如下所示...

<slot></slot>

...但我們可以新增任何想要的標記、樣式和行為。唯一的需求是元件必須包含用於頁面內容的 <slot>。例如,我們來新增一個導覽列

src/routes/+layout.svelte
<nav>
	<a href="/">Home</a>
	<a href="/about">About</a>
	<a href="/settings">Settings</a>
</nav>

<slot></slot>

如果我們為 //about/settings 建立頁面...

src/routes/+page.svelte
<h1>Home</h1>
src/routes/about/+page.svelte
<h1>About</h1>
src/routes/settings/+page.svelte
<h1>Settings</h1>

...導覽列將始終可見,而且在三個頁面之間按一下只會導致 <h1> 被取代。

佈局可以巢狀。假設我們不只有一個 /settings 頁面,而是有巢狀頁面,例如 /settings/profile/settings/notifications,並有一個共用的子選單(實際範例請參閱 github.com/settings)。

我們可以建立一個只套用於 /settings 以下頁面的佈局(同時繼承具有頂層導覽的根佈局)

src/routes/settings/+layout.svelte
<script>
	/** @type {import('./$types').LayoutData} */
	export let data;
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

<slot></slot>
src/routes/settings/+layout.svelte
<script lang="ts">
	import type { LayoutData } from './$types';
	
	export let data: LayoutData;
</script>

<h1>Settings</h1>

<div class="submenu">
	{#each data.sections as section}
		<a href="/settings/{section.slug}">{section.title}</a>
	{/each}
</div>

<slot></slot>

你可以查看下一節中 +layout.js 範例中的 data 如何填入資料。

預設情況下,每個佈局都會繼承其上方的佈局。有時候這並非你想要的,這種情況下,進階佈局 可以幫你。

+layout.js

就像 +page.svelte+page.js 載入資料一樣,你的 +layout.svelte 元件也可以從 +layout.js 中的 load 函式取得資料。

src/routes/settings/+layout.js
ts
/** @type {import('./$types').LayoutLoad} */
export function load() {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' }
]
};
}
src/routes/settings/+layout.ts
ts
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = () => {
return {
sections: [
{ slug: 'profile', title: 'Profile' },
{ slug: 'notifications', title: 'Notifications' },
],
};
};

如果 +layout.js 匯出 頁面選項prerenderssrcsr — 它們將用作子頁面的預設值。

從版面配置的 load 函數傳回的資料也可用於所有其子頁面

src/routes/settings/profile/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;

	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>
src/routes/settings/profile/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
	
	console.log(data.sections); // [{ slug: 'profile', title: 'Profile' }, ...]
</script>

在頁面之間導航時,版面配置資料通常不會改變。SvelteKit 將在必要時智慧地重新執行 load 函數。

+layout.server.js

若要在伺服器上執行版面配置的 load 函數,請將其移至 +layout.server.js,並將 LayoutLoad 類型變更為 LayoutServerLoad

+layout.js 相同,+layout.server.js 可以匯出 頁面選項prerenderssrcsr

+server

除了頁面之外,您還可以定義具有 +server.js 檔案的路由(有時稱為「API 路由」或「端點」),讓您可以完全控制回應。您的 +server.js 檔案會匯出對應於 HTTP 動詞(如 GETPOSTPATCHPUTDELETEOPTIONSHEAD)的函數,這些函數會取得 RequestEvent 參數並傳回 Response 物件。

例如,我們可以建立一個具有 GET 處理常式的 /api/random-number 路由

src/routes/api/random-number/+server.js
ts
import { error } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export function GET({ url }) {
const min = Number(url.searchParams.get('min') ?? '0');
const max = Number(url.searchParams.get('max') ?? '1');
const d = max - min;
if (isNaN(d) || d < 0) {
error(400, 'min and max must be numbers, and min must be less than max');
}
const random = min + Math.random() * d;
return new Response(String(random));
}
src/routes/api/random-number/+server.ts
ts
import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = ({ url }) => {
const min = Number(url.searchParams.get('min') ?? '0');
const max = Number(url.searchParams.get('max') ?? '1');
const d = max - min;
if (isNaN(d) || d < 0) {
error(400, 'min and max must be numbers, and min must be less than max');
}
const random = min + Math.random() * d;
return new Response(String(random));
};

傳遞給 Response 的第一個參數可以是 ReadableStream,這使得串流大量資料或建立伺服器傳送事件成為可能(除非部署到會緩衝回應的平台,例如 AWS Lambda)。

您可以使用 @sveltejs/kit 中的 errorredirectjson 方法以方便使用(但您不必這麼做)。

如果拋出錯誤(error(...) 或意外錯誤),回應將會是錯誤的 JSON 表示或備用錯誤頁面,這可透過 src/error.html 來自訂,這取決於 Accept 標頭。+error.svelte 元件在這種情況下不會被渲染。你可以在此處閱讀更多有關錯誤處理的資訊。

在建立 OPTIONS 處理常式時,請注意 Vite 會注入 Access-Control-Allow-OriginAccess-Control-Allow-Methods 標頭,除非你將它們加入,否則這些標頭在製作環境中不會存在。

接收資料

透過匯出 POST/PUT/PATCH/DELETE/OPTIONS/HEAD 處理常式,+server.js 檔案可以用來建立完整的 API

src/routes/add/+page.svelte
<script>
	let a = 0;
	let b = 0;
	let total = 0;

	async function add() {
		const response = await fetch('/api/add', {
			method: 'POST',
			body: JSON.stringify({ a, b }),
			headers: {
				'content-type': 'application/json'
			}
		});

		total = await response.json();
	}
</script>

<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}

<button on:click={add}>Calculate</button>
src/routes/add/+page.svelte
<script lang="ts">
	let a = 0;
	let b = 0;
	let total = 0;
	
	async function add() {
		const response = await fetch('/api/add', {
			method: 'POST',
			body: JSON.stringify({ a, b }),
			headers: {
				'content-type': 'application/json',
			},
		});
	
		total = await response.json();
	}
</script>

<input type="number" bind:value={a}> +
<input type="number" bind:value={b}> =
{total}

<button on:click={add}>Calculate</button>
src/routes/api/add/+server.js
ts
import { json } from '@sveltejs/kit';
/** @type {import('./$types').RequestHandler} */
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
src/routes/api/add/+server.ts
ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ request }) => {
const { a, b } = await request.json();
return json(a + b);
};

一般來說,表單動作是從瀏覽器提交資料到伺服器更好的方式。

如果匯出 GET 處理常式,HEAD 要求會傳回 GET 處理常式回應主體的 content-length

備用方法處理常式

匯出 fallback 處理常式會比對任何未處理的要求方法,包括沒有從 +server.js 專門匯出的方法,例如 MOVE

src/routes/api/add/+server.js
ts
import { json, text } from '@sveltejs/kit';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// This handler will respond to PUT, PATCH, DELETE, etc.
/** @type {import('./$types').RequestHandler} */
export async function fallback({ request }) {
return text(`I caught your ${request.method} request!`);
}
src/routes/api/add/+server.ts
ts
import { json, text } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export async function POST({ request }) {
const { a, b } = await request.json();
return json(a + b);
}
// This handler will respond to PUT, PATCH, DELETE, etc.
export const fallback: RequestHandler = async ({ request }) => {
return text(`I caught your ${request.method} request!`);
};

對於 HEAD 要求,GET 處理常式優先於 fallback 處理常式。

內容協商

+server.js 檔案可以放置在與 +page 檔案相同的目錄中,讓同一路由可以是頁面或 API 端點。為了判斷,SvelteKit 會套用下列規則

  • PUT/PATCH/DELETE/OPTIONS 要求總是會由 +server.js 處理,因為它們不適用於頁面
  • GET/POST/HEAD 要求會被視為頁面要求,如果 accept 標頭優先考慮 text/html(換句話說,這是瀏覽器頁面要求),否則它們會由 +server.js 處理。
  • GET 要求的回應會包含 Vary: Accept 標頭,這樣代理伺服器和瀏覽器才能分別快取 HTML 和 JSON 回應。

$types

在以上範例中,我們一直從 $types.d.ts 檔案匯入類型。這是 SvelteKit 在隱藏目錄中為你建立的檔案,如果你使用 TypeScript(或具有 JSDoc 類型註解的 JavaScript)來提供類型安全性,以便使用你的根檔案時。

例如,使用 PageData(或 LayoutData,對於 +layout.svelte 檔案)註解 export let data 會告訴 TypeScript,data 的類型是從 load 傳回的任何內容

src/routes/blog/[slug]/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>
src/routes/blog/[slug]/+page.svelte
<script lang="ts">
	import type { PageData } from './$types';
	
	export let data: PageData;
</script>

反過來,使用 PageLoadPageServerLoadLayoutLoadLayoutServerLoad(分別用於 +page.js+page.server.js+layout.js+layout.server.js)註解 load 函數可確保 params 和回傳值類型正確。

如果你使用 VS Code 或任何支援語言伺服器協定和 TypeScript 外掛程式的 IDE,那麼你可以完全省略這些類型!Svelte 的 IDE 工具會為你插入正確的類型,因此你可以在不自己撰寫的情況下進行類型檢查。它也與我們的命令列工具 svelte-check 搭配使用。

你可以在我們的部落格文章中進一步了解省略 $types

其他檔案

SvelteKit 會忽略路由目錄中的任何其他檔案。這表示你可以將元件和實用程式模組與需要它們的路由並置。

如果元件和模組需要由多個路由使用,建議將它們放在 $lib 中。

進一步閱讀

上一個 Web 標準
下一個 載入資料