Skip to main content

Core concepts

ルーティング

Edit this page on GitHub

SvelteKit の中心は、 ファイルシステムベースのルーター です。アプリのルート(routes) — 例えばユーザーがアクセスできる URL パス — は、コードベースのディレクトリによって定義されます:

  • src/routes は最上位のルート(the root route)です
  • src/routes/about/about ルート(route)を作成します
  • src/routes/blog/[slug]パラメータ slug を使ったルート(route)を作成します。パラメータは、ユーザーからのリクエストが /blog/hello-world のようなページに行われた場合に、動的にデータを読み込むために使用することができます

プロジェクトの設定 を編集することで、src/routes から別のディレクトリに変更することができます。

ルート(route)のディレクトリはそれぞれ1つ以上の ルートファイル(route files) を格納します。ルートファイル(route files)には + という接頭辞が付いているので、それで見分けることができます。

We'll introduce these files in a moment in more detail, but here are a few simple rules to help you remember how SvelteKit's routing works:

  • All files can run on the server
  • All files run on the client except +server files
  • +layout and +error files apply to subdirectories as well as the directory they live in

+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 では、ルート(routes)間のナビゲーションに、フレームワーク固有の <Link> コンポーネントではなく、<a> 要素を使用します。

+page.js

ページではたびたび、レンダリングの前になんらかのデータを読み込む必要があります。これに対応するため、load 関数をエクスポートする +page.js モジュールを追加しています:

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 とともに実行されます。サーバーサイドレンダリング中はサーバーで実行され、クライアントサイドナビゲーション中はブラウザで実行されます。API の詳細は load をご参照ください。

+page.js では、load だけでなくページの動作(behaviour)を設定するための値をエクスポートすることができます:

  • export const prerender = true または false または 'auto'
  • export const ssr = true または false
  • export const csr = true または false

これらに関するより詳しい情報は page options をご覧ください。

+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 によってシリアライズできなければならないということです。この API の詳細については load をご参照ください。

+page.js のように、+page.server.jspage options (prerenderssrcsr) をエクスポートできます。

また、+page.server.js ファイルは actions をエクスポートできます。load がサーバーからデータを読み取る場合、actions<form> 要素を使用してサーバーにデータを書き込むことができます。これらの使い方を学ぶには、form actions セクションをご参照ください。

+error

load 中にエラーが発生した場合、SvelteKit はデフォルトのエラーページをレンダリングします。+error.svelte を追加することで、ルート(route) ごとにエラーページをカスタマイズすることができます:

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 は、ツリーを上がって (walk up the tree) 最も近いエラー境界 (error boundary) を探します — もし上記のファイルが存在しない場合は、デフォルトのエラーページをレンダリングする前に src/routes/blog/+error.svelte を探しに行き、その次に src/routes/+error.svelte を探します。もしそれも失敗した場合は (または、最上位の +error の '上に' 位置する最上位の +layoutload 関数からエラーがスローされた場合)、SvelteKit は静的なフォールバックエラーページをレンダリングします。これは src/error.html ファイルを作成することでカスタマイズ可能です。

+layout(.server).jsload 関数の内側でエラーが発生した場合、ツリーの中で最も近くにあるエラー境界はそのレイアウトの上位にある +error.svelte ファイルです (隣ではありません)。

ルート(route)が見つからない場合 (404)、src/routes/+error.svelte (または、もしこのファイルが存在しない場合はデフォルトのエラーページ) が使われます。

エラーが handle の内側や +server.js リクエストハンドラ の内側で発生した場合は、+error.svelte は使用されません。

エラーハンドリングに関する詳細は こちら からお読み頂けます。

+layout

これまで、ページを完全に独立したコンポーネントとして扱ってきました — ナビゲーションを行うと、既存の +page.svelte コンポーネントが破棄され、新しいページコンポーネントで置き換えられます。

しかし多くのアプリでは、トップレベルのナビゲーションやフッターのように 全ての ページで表示されるべき要素があります。全ての +page.svelte にそれらを繰り返し配置する代わりに、レイアウト(layouts) に配置することができます。

+layout.svelte

全てのページに適用するレイアウトを作成するには、src/routes/+layout.svelte というファイルを作成します。デフォルトのレイアウト (あなたが作成していない場合に SvelteKit が使用するもの) は以下のようなものです…

<slot></slot>

…しかし、お望みのマークアップ(markup)、スタイル(styles)、動作(behaviour)を追加することができます。唯一の要求事項は、コンポーネントにページコンテンツのための <slot> を含めることです。例えば、nav bar を追加してみましょう:

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>

… nav は常に表示され、3つのページのための a 要素をそれぞれクリックしても <h1> が置き換わるだけです。

レイアウトは ネスト させることができます。例えば、単一の /settings ページだけでなく、/settings/profile/settings/notifications のような共有のサブメニューを持つネストしたページがあるとします (実例としては、github.com/settings をご参照ください)。

/settings 配下のページにのみ適用されるレイアウトを作成することができます (トップレベルの nav を持つ最上位のレイアウト(root layout)を継承しています):

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>

data がどのように入力されるかは、すぐ下の次のセクションにある +layout.js の例を見ればわかります。

デフォルトでは、各レイアウトはその上にあるレイアウトを継承します。そうしたくない場合は、advanced layouts が役に立つでしょう。

+layout.js

+page.svelte+page.js からデータを読み込むように、+layout.svelte コンポーネントは +layout.jsload 関数からデータを取得することができます。

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.jspage options (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 では page optionsprerenderssrcsr をエクスポートすることができます。

+server

ページと同様に、+server.js ファイル (よく 'API ルート(API route)' または 'エンドポイント(endpoint)' とも呼ばれる) でルート(routes) を定義でき、これによってレスポンスを完全にコントロールすることができます。+server.js ファイル は GETPOSTPATCHPUTDELETEOPTIONSHEAD といった HTTP verbs に対応する関数をエクスポートします。これは RequestEvent を引数に取り、Response オブジェクトを返します。

例えば、GET ハンドラを使用した /api/random-number ルート(route)を作成できます:

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 を指定することができ、大量のデータをストリームしたり、server-sent events を作成したりすることができます (AWS Lambda のような、レスポンスをバッファするプラットフォームにデプロイする場合は除きます)。

便宜上、@sveltejs/kiterrorredirectjson メソッドを使用することは可能です (ただし、使用する必要はありません)。

エラーがスローされる場合 (error(...) の場合でも、予期せぬエラーの場合でもどちらでも)、レスポンスは Accept ヘッダーに応じて、そのエラーの JSON 表現か、src/error.html でカスタマイズすることができるフォールバックエラーページとなります。この場合、+error.svelte コンポーネントはレンダリングされません。エラーハンドリングに関する詳細は こちら からお読み頂けます。

OPTIONS ハンドラを作成する場合、Vite が Access-Control-Allow-Origin ヘッダーと Access-Control-Allow-Methods ヘッダーを注入することにご注意ください。本番環境では、あなたが明示的に追加しない限り注入されないはずです。

Receiving data

+server.js ファイルは、POST/PUT/PATCH/DELETE/OPTIONS/HEAD ハンドラをエクスポートすることで、完全な 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);
};

一般的には、ブラウザからサーバーにデータを送信する方法としては form actions のほうがより良い方法です。

GET ハンドラがエクスポートされている場合、HEAD リクエストは GET ハンドラのレスポンスボディの content-length を返します。

Fallback method handler

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 リクエストの場合、fallback ハンドラより GET ハンドラが優先されます。

Content negotiation

+server.js ファイルは +page ファイルと同じディレクトリに置くことができ、これによって同じルート(route)がページにも 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 ファイルからインポートしてきました。これは、TypeScript (または JavaScript を JSDoc の型アノテーションと) 使用している場合に最上位のファイル(root files)を扱う際に型の安全性をもたらすために SvelteKit が隠しディレクトリに作成するファイルです。

例えば、export let dataPageData (または LayoutData の場合は +layout.svelte ファイル) にアノテーションを付けると、data の型は load の戻り値であると TypeScript に伝えることができます:

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>

load 関数に PageLoadPageServerLoadLayoutLoadLayoutServerLoad (それぞれ +page.js+page.server.js+layout.js+layout.server.js) というアノテーションを付けると、params と戻り値が正しく型付けされることが保証されるでしょう。

VS Code や、language server protocol と TypeScript plugin をサポートする IDE を使用している場合は、これらの型を 完全に 省略することができます! Svelte の IDE ツール類があなたのために正しい型を挿入してくれるので、あなたはご自身で型を書くことなく型チェックすることができます。これはコマンドラインツール svelte-check でも機能します。

$types の省略については、私たちのブログ記事でより詳細な情報をお読み頂けます。

その他のファイル

ルート(route)ディレクトリ内のその他のファイルは SvelteKit から無視されます。つまり、コンポーネントやユーティリティモジュールを、それらを必要とするルート(routes)に配置することができます。

コンポーネントやモジュールが複数のルート(routes)から必要な場合、$lib にそれらを配置すると良いでしょう。

その他の参考資料