SvelteKitのストリーミング(遅延ロード)を試した話
SvelteKitでは、load 関数から Promise をそのまま返すことで、データの一部を後から流し込むストリーミングが実現できます。重いクエリが完了するまでページ全体を待たせずに、段階的に表示できるのがメリットです。
基本的な仕組み
通常の load 関数はすべてのデータが揃ってからページを返しますが、Promise をそのまま返すとSvelteKitが先にHTMLを送信し、後からデータを流し込みます。
export const load = async () => {
// awaitする → すぐ表示される
const fast = await fetchFastData();
// awaitしない → Promiseのまま返す → 後から流し込まれる
const slow = fetchSlowData();
return { fast, slow };
};
Svelte側では {#await} ブロックで受け取ります。
<!-- すぐ表示 -->
<p>{data.fast}</p>
<!-- データが来るまでローディング表示 -->
{#await data.slow}
<p>読み込み中...</p>
{:then result}
<p>{result}</p>
{:catch error}
<p>エラー: {error.message}</p>
{/await}
実装例
+page.server.ts
export const load = async () => {
const slow = new Promise<string[]>(resolve => {
setTimeout(() => resolve(['記事A', '記事B', '記事C']), 2000);
});
return {
count: 42, // すぐ返るデータ
posts: slow // 2秒後に返るデータ(Promiseのまま)
};
};
+page.svelte
<script lang="ts">
let { data } = $props();
</script>
<h1>ストリーミングデモ</h1>
<!-- すぐ表示される -->
<p>記事数: {data.count} 件</p>
<!-- 2秒後に切り替わる -->
{#await data.posts}
<p>記事一覧を読み込み中...</p>
{:then posts}
<ul>
{#each posts as post}
<li>{post}</li>
{/each}
</ul>
{:catch error}
<p style="color: red">エラー: {error.message}</p>
{/await}
npm run dev(Vite)で確認すると、「記事数: 42 件」が即座に表示され、2秒後に「読み込み中…」が記事一覧に切り替わります。
{#await} の3つのブロック
| ブロック | タイミング | 用途 |
|---|---|---|
{#await promise} |
Promiseが解決する前 | ローディング表示 |
{:then value} |
Promiseが解決したとき | データ表示 |
{:catch error} |
Promiseがrejectしたとき | エラー表示 |
ローディング表示が不要な場合は省略できます。
{#await data.posts then posts}
{#each posts as post}
<li>{post}</li>
{/each}
{/await}
wrangler dev での注意点
npx wrangler dev ではストリーミングレスポンスがバッファリングされるため、「読み込み中…」の状態が表示されません。データが揃ってからまとめてレスポンスが返ってきます。
| 環境 | ストリーミング |
|---|---|
npm run dev(Vite) |
✅ 動作する |
npx wrangler dev |
❌ バッファリングされる |
| 本番Cloudflare | ✅ 動作する |
ストリーミングの動作確認は npm run dev で行い、本番はCloudflareにデプロイして確認するのが現実的です。
まとめ
load関数でPromiseをそのまま返すとストリーミングになる- Svelte側は
{#await}ブロックでローディング・完了・エラーを切り分ける - 重いDBクエリをストリーミングにすることで、ページの初期表示を速くできる
wrangler devではバッファリングされるため、動作確認はnpm run devか本番環境で行う