データフェッチング戦略
このページは実践的なデータ取得パターンとベストプラクティスを解説しています。
- 📊 データフローの詳細 - Load関数の基本的な実行順序(初級〜中級)
- ⚡ 現在のページ: データフェッチング戦略(中級〜上級)
- 🏗️ データロードアーキテクチャ - 内部実装とメカニズムの詳解(上級)
学習パス: 基本的な流れを理解 → 実践パターンを学ぶ → 内部実装を理解
データフェッチング戦略とは?
データフェッチング戦略とは、Webアプリケーションがサーバーやデータベースからデータを取得する際の「方法」と「タイミング」を最適化するためのアプローチです。適切な戦略を選択することで、以下のような効果が得られます。
- パフォーマンス向上: ページの読み込み速度を大幅に改善
- ユーザー体験の最適化: 待ち時間を減らし、段階的にコンテンツを表示
- サーバー負荷の軽減: キャッシュや並列処理で効率的なリソース利用
- エラー耐性の向上: 一部のデータ取得に失敗してもページ表示を継続
なぜ戦略が重要なのか?
単純に「データを取得して表示する」だけでは、以下のような問題が発生します。
- 遅いAPIがボトルネックに: 1つの遅いAPIがページ全体の表示を遅延させる
- 無駄なサーバー負荷: 同じデータを何度も取得してしまう
- 全か無かの失敗: 1つのデータ取得が失敗すると全体がエラーになる
- 静的な表示: リアルタイムで更新されるデータに対応できない
これらの問題を解決するために、SvelteKitでは様々なデータフェッチング戦略を提供しています。
戦略の分類
SvelteKitのデータフェッチング戦略は、大きく「基本戦略」と「高度な戦略」に分けられます。
基本戦略(Load関数の基礎)
Load関数の基礎ページ で解説している基本的なパターンも、重要なデータフェッチング戦略です。
- 親子間のデータ共有:
parent()
を使った効率的なデータ継承 - 認証チェック: Server Loadでの認証状態確認
- APIプロキシ: 外部APIの安全な呼び出し
- 型安全なデータ取得: TypeScriptによる型推論の活用
高度な戦略(このページで解説)
以下の表から、学びたい高度な戦略に直接ジャンプできます。
戦略 | 説明 | 使用場面 |
---|---|---|
ストリーミングSSR | 段階的にコンテンツを送信し、初期表示を高速化 | 大量データのページ、遅いAPIの呼び出し |
並列データ取得 | Promise.allで複数のデータを同時取得 | 複数の独立したAPIからのデータ取得 |
キャッシング戦略 | メモリやブラウザキャッシュでパフォーマンス向上 | 頻繁にアクセスされる静的データ |
リアルタイム更新 | SSEやinvalidateで動的にデータ更新 | ライブデータ、チャット、通知 |
条件付きフェッチング | ユーザー状態に応じてデータを動的に変更 | 認証、権限管理、デバイス最適化 |
エラー境界とフォールバック | 部分的な失敗でもページ表示を継続 | 外部API依存、ネットワーク不安定時 |
パフォーマンス監視 | データ取得のタイミングを測定・最適化 | ボトルネック特定、継続的改善 |
まず Load関数の基礎 で基本戦略を理解してから、このページの高度な戦略に進むことをおすすめします。
ストリーミングSSRの詳細は ストリーミングSSR の専用ページで解説しています。
パフォーマンスのヒント
効率的なデータ取得のための重要なテクニックを紹介します。
並列データ取得の重要性
複数のデータソースから情報を取得する際は、並列処理を活用することで大幅にパフォーマンスを改善できます。
// ❌ 悪い例:順次実行(遅い)
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch('/api/posts').then(r => r.json());
const comments = await fetch('/api/comments').then(r => r.json());
// 合計時間 = user取得時間 + posts取得時間 + comments取得時間
// ✅ 良い例:並列実行(速い)
const [user, posts, comments] = await Promise.all([
fetch('/api/user').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
// 合計時間 = 最も遅いリクエストの時間のみ
並列データ取得パターン
複数のデータソースから情報を取得する場合、適切な並列処理戦略を選択することで、大幅なパフォーマンス向上を実現できます。このセクションでは、実践的な並列データ取得のパターンを紹介します。
基本的な並列処理の概念
SvelteKitでの並列データ取得実装
以下のシーケンス図は、SvelteKitの+page.ts
で実際にどのように並列データ取得が行われるかを示しています。
Promise.allを使った最適化
Promise.all()
を使用することで、複数の非同期処理を同時に実行し、すべての処理が完了するのを待つことができます。これは、独立したデータを取得する際の最も基本的で効果的なパターンです。
// +page.ts
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ fetch, params }) => {
// 複数のAPIエンドポイントから並列でデータ取得
const [user, posts, comments] = await Promise.all([
fetch(`/api/users/${params.id}`).then(r => r.json()),
fetch(`/api/users/${params.id}/posts`).then(r => r.json()),
fetch(`/api/users/${params.id}/comments`).then(r => r.json())
]);
return {
user,
posts,
comments
};
};
依存関係がある場合の最適化
データ間に依存関係がある場合でも、可能な限り並列処理を活用できます。必要最小限のデータを先に取得し、その結果を使って残りのデータを並列で取得するパターンです。
export const load: PageLoad = async ({ fetch, params }) => {
// ユーザー情報を先に取得
const user = await fetch(`/api/users/${params.id}`).then(r => r.json());
// ユーザーの情報に基づいて並列取得
const [posts, followers] = await Promise.all([
fetch(`/api/posts?author=${user.id}`).then(r => r.json()),
fetch(`/api/users/${user.id}/followers`).then(r => r.json())
]);
return {
user,
posts,
followers
};
};
キャッシング戦略
適切なキャッシング戦略により、不要なネットワークリクエストを削減し、アプリケーションのパフォーマンスを大幅に向上させることができます。キャッシングは、ブラウザレベル、メモリレベル、そしてCDNレベルで実装できます。
基本的なキャッシング概念
SvelteKitでのキャッシング実装
以下のシーケンス図は、SvelteKitで+page.server.ts
とキャッシュユーティリティを使用した実際のキャッシング実装を示しています。
ブラウザキャッシュの活用
HTTPヘッダーを適切に設定することで、ブラウザの組み込みキャッシュ機能を活用できます。静的なデータには長いキャッシュ期間を設定し、動的なデータには短い期間またはキャッシュ無効を設定します。
export const load: PageLoad = async ({ fetch }) => {
// キャッシュを活用
const staticData = await fetch('/api/static-data', {
headers: {
'Cache-Control': 'max-age=3600' // 1時間キャッシュ
}
}).then(r => r.json());
// 常に最新データを取得
const dynamicData = await fetch('/api/dynamic-data', {
cache: 'no-store'
}).then(r => r.json());
return {
staticData,
dynamicData
};
};
メモリキャッシュの実装
サーバーサイドでメモリキャッシュを実装することで、データベースへのアクセスを削減し、レスポンス時間を短縮できます。以下は、シンプルなTTL(Time To Live)ベースのキャッシュ実装例です。
// lib/cache.ts
const cache = new Map<string, { data: any; timestamp: number }>();
const CACHE_DURATION = 5 * 60 * 1000; // 5分
export async function getCachedData<T>(
key: string,
fetcher: () => Promise<T>
): Promise<T> {
const cached = cache.get(key);
const now = Date.now();
if (cached && now - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
const data = await fetcher();
cache.set(key, { data, timestamp: now });
return data;
}
上記のキャッシュユーティリティを使用して、頻繁にアクセスされるが更新頻度の低いデータをキャッシュします。
// +page.server.ts
import { getCachedData } from '$lib/cache';
export const load: PageServerLoad = async () => {
const data = await getCachedData('popular-posts', async () => {
return await db.post.findMany({
where: { popular: true },
take: 10
});
});
return { posts: data };
};
リアルタイム更新
リアルタイムでデータを更新する機能は、現代的なWebアプリケーションに欠かせません。SvelteKitは、invalidate関数やServer-Sent Events(SSE)、WebSocketなど、様々なリアルタイム更新の手法をサポートしています。
基本的なリアルタイム更新概念
SvelteKitでのリアルタイム更新実装
以下のシーケンス図は、SvelteKitで+page.svelte
と+server.ts
を使用してSSEによるリアルタイム更新を実装する方法を示しています。
invalidateを使った更新
invalidate
関数を使用すると、特定のURLに関連するLoad関数を再実行できます。これは、定期的なデータ更新や、ユーザーアクションに応じたデータの再取得に適しています。
// +page.svelte
<script lang="ts">
import { invalidate } from '$app/navigation';
import { onMount } from 'svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
onMount(() => {
// 30秒ごとにデータを更新
const interval = setInterval(() => {
invalidate('/api/live-data');
}, 30000);
return () => clearInterval(interval);
});
</script>
Server-Sent Events (SSE)
Server-Sent Events(SSE)は、サーバーからクライアントへの単方向リアルタイム通信を実現する技術です。WebSocketよりもシンプルで、HTTPプロトコル上で動作するため、ファイアウォールやプロキシの問題が少ないという利点があります。
// +server.ts (APIエンドポイント)
import type { RequestHandler } from './$types';
export const GET: RequestHandler = () => {
const stream = new ReadableStream({
start(controller) {
const interval = setInterval(() => {
const data = JSON.stringify({
time: new Date().toISOString()
});
controller.enqueue(`data: ${data}\n\n`);
}, 1000);
return () => clearInterval(interval);
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache'
}
});
};
クライアント側では、EventSource
APIを使ってSSEストリームに接続し、リアルタイムでデータを受信します。
<!-- +page.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
let data = $state<any>(null);
onMount(() => {
const eventSource = new EventSource('/api/stream');
eventSource.onmessage = (event) => {
data = JSON.parse(event.data);
};
return () => eventSource.close();
});
</script>
条件付きフェッチング
条件付きフェッチングは、ユーザーの状態、デバイス、権限などに基づいて、取得するデータを動的に変更する技術です。これにより、必要なデータのみを効率的に取得し、パフォーマンスとユーザー体験を最適化できます。
基本的な条件付きフェッチング概念
SvelteKitでの条件付きフェッチング実装
以下のシーケンス図は、SvelteKitでhooks.server.ts
と+page.server.ts
を使用して認証ベースの条件付きフェッチングを実装する方法を示しています。
認証に基づくデータ取得
ユーザーの認証状態に応じて、異なるデータセットを返します。未認証ユーザーには公開情報のみ、認証済みユーザーには追加の個人情報を提供するパターンです。
// +page.server.ts
export const load: PageServerLoad = async ({ locals }) => {
const baseData = await getPublicData();
if (!locals.user) {
return { ...baseData, user: null };
}
// 認証済みユーザー向けの追加データ
const [profile, preferences] = await Promise.all([
getUserProfile(locals.user.id),
getUserPreferences(locals.user.id)
]);
return {
...baseData,
user: locals.user,
profile,
preferences
};
};
デバイスに応じた最適化
User-Agentヘッダーを解析して、デバイスの種類を判定し、それに応じて異なるデータ量や品質を提供します。モバイルデバイスには軽量版、デスクトップには詳細版を提供することで、最適なユーザー体験を実現します。
export const load: PageServerLoad = async ({ request }) => {
const userAgent = request.headers.get('user-agent') || '';
const isMobile = /mobile/i.test(userAgent);
if (isMobile) {
// モバイル向け軽量データ
return {
items: await getLightweightData()
};
}
// デスクトップ向け詳細データ
return {
items: await getDetailedData()
};
};
エラー境界とフォールバック
堅牢なアプリケーションを構築するには、エラーを適切に処理し、部分的な失敗に対してもユーザーに価値を提供できるようにすることが重要です。SvelteKitは、様々なレベルでエラーを処理する仕組みを提供しています。
基本的なエラーハンドリング概念
SvelteKitでのエラーハンドリング実装
以下のシーケンス図は、SvelteKitで+page.ts
と+error.svelte
を使用してエラー境界とフォールバックを実装する方法を示しています。
部分的エラーの処理
Promise.allSettled()
を使用することで、複数の非同期処理のうち一部が失敗しても、成功した処理の結果を取得できます。これにより、完全な失敗を避け、利用可能なデータでページを表示できます。
export const load: PageLoad = async ({ fetch }) => {
const results = await Promise.allSettled([
fetch('/api/main-data').then(r => r.json()),
fetch('/api/optional-data').then(r => r.json()),
fetch('/api/recommendations').then(r => r.json())
]);
return {
mainData: results[0].status === 'fulfilled'
? results[0].value
: null,
optionalData: results[1].status === 'fulfilled'
? results[1].value
: { fallback: true },
recommendations: results[2].status === 'fulfilled'
? results[2].value
: []
};
};
パフォーマンス監視
パフォーマンスの継続的な監視は、アプリケーションの品質を維持するために不可欠です。問題を早期に発見し、ユーザー体験の劣化を防ぐことができます。
基本的なパフォーマンス測定
パフォーマンス測定の基本的な概念と手法を紹介します。
SvelteKitでのパフォーマンス監視実装
以下のシーケンス図は、SvelteKitでパフォーマンス監視を実装する方法を示しています。
タイミング測定
performance.now()
を使用して、データ取得にかかった時間を正確に測定します。これにより、パフォーマンスのボトルネックを特定し、最適化の対象を明確にできます。
export const load: PageLoad = async ({ fetch }) => {
const start = performance.now();
const data = await fetch('/api/data').then(r => r.json());
const duration = performance.now() - start;
// パフォーマンスログ
if (duration > 1000) {
console.warn(`Slow request: ${duration}ms`);
}
return { data };
};
ベストプラクティス
重要なデータを優先
- クリティカルなデータは即座に返す
- 補足的なデータはストリーミング
並列処理を最大活用
- 独立したデータは
Promise.all()
で同時取得
- 独立したデータは
適切なキャッシュ戦略
- 静的データは積極的にキャッシュ
- 動的データは必要に応じて無効化
エラーに備える
Promise.allSettled()
で部分的エラーに対応- フォールバックデータを用意
次のステップ
- SPAモードとデータ無効化 - SPAモードとデータ更新の詳細
- ストリーミングSSR - 段階的なコンテンツ配信
- フォーム処理とActions - データの送信と処理