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   → そのページだけで必要なデータ(記事の内容など)

「全ページで使うものはレイアウト、そのページだけで使うものはページ」という一貫したルールです。

← トップページに戻る