データフェッチング戦略

関連ページ

このページは実践的なデータ取得パターンとベストプラクティスを解説しています。

学習パス: 基本的な流れを理解 → 実践パターンを学ぶ → 内部実装を理解

データフェッチング戦略とは?

データフェッチング戦略とは、Webアプリケーションがサーバーやデータベースからデータを取得する際の「方法」と「タイミング」を最適化するためのアプローチです。適切な戦略を選択することで、以下のような効果が得られます。

  • パフォーマンス向上: ページの読み込み速度を大幅に改善
  • ユーザー体験の最適化: 待ち時間を減らし、段階的にコンテンツを表示
  • サーバー負荷の軽減: キャッシュや並列処理で効率的なリソース利用
  • エラー耐性の向上: 一部のデータ取得に失敗してもページ表示を継続

なぜ戦略が重要なのか?

単純に「データを取得して表示する」だけでは、以下のような問題が発生します。

  1. 遅いAPIがボトルネックに: 1つの遅いAPIがページ全体の表示を遅延させる
  2. 無駄なサーバー負荷: 同じデータを何度も取得してしまう
  3. 全か無かの失敗: 1つのデータ取得が失敗すると全体がエラーになる
  4. 静的な表示: リアルタイムで更新されるデータに対応できない

これらの問題を解決するために、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())
]);
// 合計時間 = 最も遅いリクエストの時間のみ
typescript

並列データ取得パターン

複数のデータソースから情報を取得する場合、適切な並列処理戦略を選択することで、大幅なパフォーマンス向上を実現できます。このセクションでは、実践的な並列データ取得のパターンを紹介します。

基本的な並列処理の概念

ダイアグラムを読み込み中...

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
  };
};
typescript

依存関係がある場合の最適化

データ間に依存関係がある場合でも、可能な限り並列処理を活用できます。必要最小限のデータを先に取得し、その結果を使って残りのデータを並列で取得するパターンです。

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
  };
};
typescript

キャッシング戦略

適切なキャッシング戦略により、不要なネットワークリクエストを削減し、アプリケーションのパフォーマンスを大幅に向上させることができます。キャッシングは、ブラウザレベル、メモリレベル、そして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
  };
};
typescript

メモリキャッシュの実装

サーバーサイドでメモリキャッシュを実装することで、データベースへのアクセスを削減し、レスポンス時間を短縮できます。以下は、シンプルな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;
}
typescript

上記のキャッシュユーティリティを使用して、頻繁にアクセスされるが更新頻度の低いデータをキャッシュします。

// +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 };
};
typescript

リアルタイム更新

リアルタイムでデータを更新する機能は、現代的な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>
typescript

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'
    }
  });
};
typescript

クライアント側では、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>
svelte

条件付きフェッチング

条件付きフェッチングは、ユーザーの状態、デバイス、権限などに基づいて、取得するデータを動的に変更する技術です。これにより、必要なデータのみを効率的に取得し、パフォーマンスとユーザー体験を最適化できます。

基本的な条件付きフェッチング概念

ダイアグラムを読み込み中...

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
  };
};
typescript

デバイスに応じた最適化

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()
  };
};
typescript

エラー境界とフォールバック

堅牢なアプリケーションを構築するには、エラーを適切に処理し、部分的な失敗に対してもユーザーに価値を提供できるようにすることが重要です。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
      : []
  };
};
typescript

パフォーマンス監視

パフォーマンスの継続的な監視は、アプリケーションの品質を維持するために不可欠です。問題を早期に発見し、ユーザー体験の劣化を防ぐことができます。

基本的なパフォーマンス測定

パフォーマンス測定の基本的な概念と手法を紹介します。

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 };
};
typescript

ベストプラクティス

  1. 重要なデータを優先

    • クリティカルなデータは即座に返す
    • 補足的なデータはストリーミング
  2. 並列処理を最大活用

    • 独立したデータはPromise.all()で同時取得
  3. 適切なキャッシュ戦略

    • 静的データは積極的にキャッシュ
    • 動的データは必要に応じて無効化
  4. エラーに備える

    • Promise.allSettled()で部分的エラーに対応
    • フォールバックデータを用意

次のステップ

Last update at: 2025/09/16 01:10:33