SvelteKitのload関数の詳細(depends・invalidate・parent)を整理した話

SvelteKitの load 関数を深掘りします。+page.ts+page.server.ts の使い分け、parent() で親のデータを受け取る方法、dependsinvalidate によるリロードなしのデータ再取得を整理します。


load 関数の種類

+page.ts          → クライアント・サーバー両方で実行される
+page.server.ts   → サーバーのみで実行される(DBアクセス・認証など)
+layout.ts        → レイアウト用(クライアント・サーバー両方)
+layout.server.ts → レイアウト用(サーバーのみ)

+page.ts+page.server.ts の使い分け

結論:迷ったら +page.server.ts で十分

+page.server.ts はDBアクセス・外部API・認証・環境変数など、ほぼすべての用途をカバーできます。+page.ts が必要になるのは特殊なケースに限られます。

// +page.server.ts を使うべきケース(ほとんどのケース)
export const load = async ({ depends, platform, locals }) => {
  // DBアクセス・認証・外部APIすべて対応できる
  const posts = await platform!.env.DB
    .prepare('SELECT * FROM posts')
    .all();
  return { posts: posts.results };
};
// +page.ts が必要なケース(特殊)
export const load = async () => {
  // localStorageはブラウザにしか存在しないためserver.tsでは使えない
  const theme = localStorage.getItem('theme');
  return { theme };
};
状況 結論
DBアクセス・認証・環境変数 +page.server.ts 一択
外部APIを叩くだけ +page.server.ts で十分
localStorage など使う +page.ts が必要
SSR完全無効化 +page.ts が必要

parent() で親のデータを受け取る

子の load 関数から親レイアウトのデータを参照できます。

// src/routes/+layout.server.ts
export const load = async ({ locals }) => {
  return { user: locals.user };
};
// src/routes/posts/+page.server.ts
export const load = async ({ parent, platform }) => {
  const { user } = await parent();  // 親レイアウトの data を受け取る

  const posts = await platform!.env.DB
    .prepare('SELECT * FROM posts WHERE user_id = ?')
    .bind(user.id)
    .all();

  return { posts: posts.results };
};

dependsinvalidate でデータを再取得する

depends でload関数に識別子を付けて、invalidate でその識別子を無効化するとload関数が再実行されます。ページリロードなしにデータを最新化できます。

// src/routes/+page.server.ts
export const load: PageServerLoad = async ({ depends, platform }) => {
  depends('app:posts');  // この load に識別子を付ける

  const result = await platform?.env.DB
    .prepare('SELECT * FROM posts')
    .all();

  return { posts: result?.results ?? [] };
};
<!-- src/routes/+page.svelte -->
<script lang="ts">
  import { invalidate } from '$app/navigation';

  let { data } = $props();
  let refreshing = $state(false);

  async function handleRefresh() {
    refreshing = true;
    await invalidate('app:posts');  // 'app:posts' を持つ load 関数を再実行
    refreshing = false;
  }
</script>

<button onclick={handleRefresh} disabled={refreshing}>
  {refreshing ? '更新中...' : '最新データに更新'}
</button>

<ul>
  {#each data.posts as post}
    <li>{post.title}</li>
  {/each}
</ul>

ポイント: actions + use:enhance の組み合わせではSvelteKitが自動的にloadを再実行するため invalidate は不要です。fetch で直接APIを叩くような場面で invalidate が活きます。


invalidateAll で全データを再取得する

全てのload関数を再実行したいときは invalidateAll を使います。

<script lang="ts">
  import { invalidateAll } from '$app/navigation';

  async function handleRefresh() {
    await invalidateAll();  // 全 load 関数を再実行
  }
</script>

<button onclick={handleRefresh}>最新データに更新</button>

まとめ

内容
+page.server.ts DBや認証が必要なときはこちら。ほとんどのケースで十分
+page.ts localStorage など使う特殊なケースのみ
parent() 親レイアウトのloadデータを子で使う
depends('識別子') load関数に再実行トリガーの識別子を付ける
invalidate('識別子') 特定のloadだけ再実行する
invalidateAll() 全てのloadを再実行する
← トップページに戻る