SvelteKitの+layout.svelteと+page.svelteの役割の切り分けを整理した話
SvelteKitで迷いやすい +layout.svelte と +page.svelte の役割の切り分けを整理します。
一言で言うと
+layout.svelte → 「額縁」(どのページでも変わらない部分)
+page.svelte → 「絵」(ページごとに変わる部分)
具体的なイメージ
| 領域 | 担当ファイル |
|---|---|
| ヘッダー(ロゴ・ナビゲーション) | +layout.svelte |
| ページ固有のコンテンツ | +page.svelte |
| フッター | +layout.svelte |
コードで見る
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';
type Props = { children: Snippet };
let { children }: Props = $props();
</script>
<!-- ここはどのページでも表示される -->
<header>
<a href="/">ロゴ</a>
<nav>
<a href="/">トップ</a>
<a href="/about">About</a>
</nav>
</header>
<!-- ここに各ページのコンテンツが差し込まれる -->
<main>
{@render children()}
</main>
<!-- ここもどのページでも表示される -->
<footer>© 2026 My Blog</footer>
<!-- src/routes/+page.svelte (トップページ)-->
<h1>トップページ</h1>
<p>ようこそ</p>
<!-- src/routes/about/+page.svelte (Aboutページ)-->
<h1>Aboutページ</h1>
<p>このサイトについて</p>
レンダリング結果はこうなります。
/ にアクセスしたとき:
ヘッダー
「トップページ / ようこそ」 ← +page.svelte の内容
フッター
/about にアクセスしたとき:
ヘッダー
「Aboutページ / このサイトについて」 ← +page.svelte の内容
フッター
ページが切り替わってもヘッダーとフッターは再レンダリングされません。{@render children()} の部分だけが差し替わります。
何をどちらに書くかの判断基準
| 書く内容 | どちらに書く | 理由 |
|---|---|---|
| ヘッダー・ナビゲーション | +layout.svelte |
全ページ共通 |
| フッター | +layout.svelte |
全ページ共通 |
| ログインユーザー名の表示 | +layout.svelte |
全ページで必要 |
| ページタイトル・見出し | +page.svelte |
ページごとに異なる |
| 記事の本文・一覧 | +page.svelte |
ページごとに異なる |
| そのページ専用のボタン | +page.svelte |
ページごとに異なる |
ネストしたレイアウトの場合
src/routes/
+layout.svelte ← 全体(ヘッダー・フッター)
posts/
+layout.svelte ← posts配下だけ(サイドバーなど)
+page.svelte ← /posts のコンテンツ
[id]/
+page.svelte ← /posts/[id] のコンテンツ
| 領域 | 担当ファイル |
|---|---|
| ヘッダー | +layout.svelte(全体) |
| サイドバー | posts/+layout.svelte |
| 記事コンテンツ | posts/[id]/+page.svelte |
| フッター | +layout.svelte(全体) |
posts/+layout.svelte は「全体レイアウトの children の中」に差し込まれ、さらにその中に +page.svelte が差し込まれます。
+layout.server.ts と +page.server.ts の切り分けも同じ考え方
+layout.server.ts → 全ページで必要なデータ(ログインユーザーなど)
+page.server.ts → そのページだけで必要なデータ(記事の内容など)
「全ページで使うものはレイアウト、そのページだけで使うものはページ」という一貫したルールです。