データロードアーキテクチャ

関連ページ

このページはSvelteKitの内部実装とデータ処理メカニズムを解説しています。

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

SvelteKitのデータロードシステムは、高度に最適化された内部メカニズムによって動作しています。このページでは、Load関数がどのように実装され、データがどのように処理されるかを内部実装の観点から詳しく解説します。

Load関数の内部処理フロー

SvelteKitがLoad関数を実行する際の内部処理の流れを理解することで、パフォーマンス最適化やデバッグが容易になります。

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

RequestEventオブジェクトの構造

Load関数に渡されるeventオブジェクトは、リクエストに関するすべての情報を含む重要なオブジェクトです。このオブジェクトはSvelteKitが自動的に生成し、Load関数やHooksに渡されます。

// SvelteKit内部でのRequestEvent生成(簡略化)
// このインターフェースは、Load関数が受け取るeventパラメータの型定義です
interface RequestEvent {
  // コアプロパティ
  request: Request;           // 標準のWeb Request API
  url: URL;                  // パースされたURL
  params: Record<string, string>;  // ルートパラメータ
  route: RouteDefinition;    // マッチしたルート情報
  
  // 拡張プロパティ
  locals: App.Locals;        // サーバー間で共有されるデータ
  platform?: App.Platform;  // プラットフォーム固有の情報
  
  // メソッド
  fetch: typeof fetch;       // 拡張されたfetch関数
  setHeaders: (headers: Record<string, string>) => void;
  cookies: Cookies;          // Cookie操作API
  
  // 内部使用
  depends: (...deps: string[]) => void;  // 依存関係の登録
  parent: () => Promise<Record<string, any>>;  // 親のデータ取得
}

// 実際の内部実装(概念的な例)
// SvelteKitが内部でRequestEventを生成する際の処理を簡略化したものです
// 実際のコードはより複雑ですが、基本的な仕組みは以下のようになっています
class RequestEventImpl implements RequestEvent {
  private _dependencies = new Set<string>();
  private _headers: Headers = new Headers();
  
  constructor(
    private _request: Request,
    private _route: RouteDefinition,
    private _params: Record<string, string>
  ) {
    this.url = new URL(_request.url);
    this.fetch = this.createEnhancedFetch();
  }
  
  // fetch関数の拡張実装
  private createEnhancedFetch(): typeof fetch {
    return async (input: RequestInfo, init?: RequestInit) => {
      // URLを依存関係として自動登録
      const url = typeof input === 'string' ? input : input.url;
      this._dependencies.add(url);
      
      // Cookieの自動転送
      const headers = new Headers(init?.headers);
      headers.set('cookie', this._request.headers.get('cookie') || '');
      
      // 実際のfetch実行
      const response = await fetch(input, {
        ...init,
        headers
      });
      
      return response;
    };
  }
  
  depends(...deps: string[]) {
    deps.forEach(dep => this._dependencies.add(dep));
  }
  
  setHeaders(headers: Record<string, string>) {
    Object.entries(headers).forEach(([key, value]) => {
      this._headers.set(key, value);
    });
  }
}
typescript

プロパティ一覧

プロパティ説明実行環境
requestRequest標準のWeb Request APIオブジェクト。HTTPリクエストの詳細情報を含む両方
urlURLパース済みのURLオブジェクト。クエリパラメータやパス情報に簡単にアクセス可能両方
paramsRecord<string, string>動的ルートパラメータ。例:/posts/[id]id部分両方
routeRouteDefinitionマッチしたルートの定義情報。ルートIDやパターンを含む両方
localsApp.Localsリクエスト間で共有されるデータ。主にHooksで設定される(例:ユーザー情報)両方
platformApp.Platform | undefinedプラットフォーム固有の情報(Cloudflare Workers、Vercel等)サーバー
cookiesCookiesCookie操作用のユーティリティオブジェクト両方
isDataRequestbooleanクライアントサイドナビゲーションによるデータリクエストかどうか両方
isSubRequestbooleanサブリクエスト(内部的なAPI呼び出し等)かどうかサーバー

メソッド一覧

メソッド説明実行環境使用例
fetchtypeof fetch拡張されたfetch関数。Cookie転送と依存関係追跡を自動化両方await event.fetch('/api/data')
depends(...deps: string[]) => voidカスタム依存関係を登録。invalidate()での無効化対象を設定両方event.depends('custom:data')
parent() => Promise<Record<string, any>>親レイアウトのLoad関数の結果を取得両方const parent = await event.parent()
setHeaders(headers: Record<string, string>) => voidレスポンスヘッダーを設定(キャッシュ制御等)サーバーevent.setHeaders({ 'cache-control': 'max-age=3600' })
getClientAddress() => stringクライアントのIPアドレスを取得サーバーconst ip = event.getClientAddress()

cookies オブジェクトのメソッド

メソッド説明使用例
get(name)Cookieの値を取得event.cookies.get('session')
getAll()すべてのCookieを取得event.cookies.getAll()
set(name, value, options)Cookieを設定event.cookies.set('theme', 'dark', { path: '/', httpOnly: true })
delete(name, options)Cookieを削除event.cookies.delete('session', { path: '/' })
serialize(name, value, options)Cookie文字列を生成event.cookies.serialize('auth', token, { secure: true })

実際の使用例

// +page.server.ts でのRequestEvent活用例
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async (event) => {
  // 1. URLパラメータとクエリパラメータの取得
  const postId = event.params.id;  // /posts/[id] の id部分
  const page = event.url.searchParams.get('page') || '1';
  
  // 2. 認証情報の確認(Hooksでセットされたlocals)
  const user = event.locals.user;
  if (!user) {
    // 3. レスポンスヘッダーの設定(キャッシュなし)
    event.setHeaders({
      'cache-control': 'no-store'
    });
    return { authenticated: false };
  }
  
  // 4. Cookie の読み取り
  const theme = event.cookies.get('theme') || 'light';
  
  // 5. 拡張fetch関数でAPIコール(Cookie自動転送)
  const response = await event.fetch(`/api/posts/${postId}?page=${page}`);
  const post = await response.json();
  
  // 6. カスタム依存関係の登録
  event.depends(`post:${postId}`);
  
  // 7. キャッシュ制御ヘッダーの設定
  event.setHeaders({
    'cache-control': 'public, max-age=3600'  // 1時間キャッシュ
  });
  
  return {
    post,
    theme,
    user: { name: user.name, id: user.id }
  };
};

// +page.ts でのクライアントサイド処理
import type { PageLoad } from './$types';

export const load: PageLoad = async (event) => {
  // 8. 親レイアウトのデータ取得
  const parentData = await event.parent();
  console.log('Layout data:', parentData);
  
  // 9. データリクエストかどうかの判定
  if (event.isDataRequest) {
    // クライアントサイドナビゲーションの場合
    console.log('Client-side navigation detected');
  }
  
  // 10. 追加のAPIコール
  const comments = await event.fetch(`/api/posts/${event.params.id}/comments`);
  
  return {
    comments: await comments.json(),
    layoutTheme: parentData.theme  // 親から継承
  };
};
typescript

キャッシュメカニズムの詳細

SvelteKitの内部キャッシュは、**クライアントサイド(ブラウザ)**で効率的なデータ管理を実現する重要な仕組みです。このキャッシュはクライアントサイドルーターによって管理され、ページ間のナビゲーション時にデータの再利用を可能にします。

実行環境について

ここで説明するキャッシュメカニズムはクライアントサイドのみで動作します。

  • クライアント: ナビゲーション時にLoad関数の結果をメモリにキャッシュし、invalidate()まで再利用
  • サーバー: SSR時は毎回新規にLoad関数を実行(キャッシュなし・ステートレス)

サーバーサイドでキャッシュが必要な場合は、RedisやMemcachedなどの外部キャッシュを別途実装する必要があります。

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

キャッシュ層の実装(クライアントサイド)

クライアントサイドルーターは、Load関数の結果をブラウザのメモリ上にキャッシュし、invalidate()による無効化まで再利用します。以下は、その仕組みの概念的な実装です。

// クライアントサイドでのキャッシュ管理(概念的な実装)
// このコードはブラウザ上で動作し、ページナビゲーション時の
// Load関数の結果をメモリに保持して再利用します
interface CacheEntry {
  data: any;
  timestamp: number;
  dependencies: Set<string>;
  ttl?: number;  // Time To Live
}

class LoadFunctionCache {
  private cache = new Map<string, CacheEntry>();
  private dependencies = new Map<string, Set<string>>();
  
  // キャッシュエントリの保存
  set(key: string, data: any, deps: Set<string>, ttl?: number) {
    this.cache.set(key, {
      data,
      timestamp: Date.now(),
      dependencies: deps,
      ttl
    });
    
    // 逆引き用の依存関係マップを更新
    deps.forEach(dep => {
      if (!this.dependencies.has(dep)) {
        this.dependencies.set(dep, new Set());
      }
      this.dependencies.get(dep)!.add(key);
    });
  }
  
  // キャッシュの取得
  get(key: string): any | null {
    const entry = this.cache.get(key);
    if (!entry) return null;
    
    // TTLチェック
    if (entry.ttl && Date.now() - entry.timestamp > entry.ttl) {
      this.delete(key);
      return null;
    }
    
    return entry.data;
  }
  
  // 依存関係に基づく無効化
  invalidate(dependency: string) {
    const affected = this.dependencies.get(dependency);
    if (!affected) return;
    
    // 影響を受けるすべてのキャッシュエントリを削除
    affected.forEach(key => {
      this.delete(key);
    });
    
    // 依存関係マップからも削除
    this.dependencies.delete(dependency);
  }
  
  // キャッシュエントリの削除
  private delete(key: string) {
    const entry = this.cache.get(key);
    if (!entry) return;
    
    // 依存関係マップから削除
    entry.dependencies.forEach(dep => {
      const keys = this.dependencies.get(dep);
      if (keys) {
        keys.delete(key);
        if (keys.size === 0) {
          this.dependencies.delete(dep);
        }
      }
    });
    
    this.cache.delete(key);
  }
  
  // 全キャッシュクリア
  clear() {
    this.cache.clear();
    this.dependencies.clear();
  }
}
typescript

invalidate()の内部動作(クライアントサイド)

invalidate()関数は、クライアントサイドで特定のURLやカスタム識別子に依存するキャッシュを無効化し、関連するLoad関数を再実行します。これはブラウザ上でのみ動作し、サーバーサイドのデータには影響しません。

// クライアントサイドでのinvalidate関数の内部実装
// この関数は$app/navigationからエクスポートされ、
// ブラウザ上でキャッシュを手動で無効化する際に使用します
async function invalidate(href: string | ((href: URL) => boolean)) {
  const cache = getLoadFunctionCache();
  
  if (typeof href === 'string') {
    // 文字列の場合は直接無効化
    cache.invalidate(href);
  } else {
    // 関数の場合はすべてのキャッシュエントリをチェック
    cache.forEach((entry, key) => {
      try {
        const url = new URL(key, location.origin);
        if (href(url)) {
          cache.invalidate(key);
        }
      } catch {
        // URLパースエラーは無視
      }
    });
  }
  
  // 影響を受けるLoad関数を再実行
  await rerunAffectedLoadFunctions();
}
typescript

Request/Responseライフサイクル

リクエストからレスポンスまでの完全なライフサイクルを理解することで、データの流れを完全に制御できます。

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

内部的なfetch関数の拡張

Load関数内で使用できるfetch関数は、標準のFetch APIを拡張したもので、Cookie転送、依存関係追跡、内部API最適化などの機能が追加されています。

fetch関数の取得方法と使い分け

取得方法使用場所構文特徴
引数から取得Load関数async ({ fetch }) => ...Cookie自動転送、依存関係追跡、SSR最適化
eventから取得Load関数async (event) => event.fetch(...)上記と同じ(別の書き方)
引数から取得Hooksasync ({ event }) => event.fetch(...)リクエスト処理のカスタマイズ
引数から取得Form Actionsasync ({ fetch }) => ...フォーム送信時のAPI呼び出し
グローバルコンポーネント/その他fetch(...)標準のFetch API(インポート不要)
使い分けのポイント
  • Load関数内では必ず引数のfetchを使用:Cookie転送や依存関係追跡が自動化される
  • コンポーネント内ではグローバルfetchでOK:クライアントサイドのみの処理
  • インポートは不要:Node.js 18+およびモダンブラウザでは標準で利用可能
// SvelteKitが提供する拡張fetch関数の実装
// この関数はLoad関数のevent.fetchとして提供され、
// 通常のfetchにSvelteKit固有の機能を追加しています
function createEnhancedFetch(event: RequestEvent): typeof fetch {
  return async function fetch(
    input: RequestInfo | URL,
    init?: RequestInit
  ): Promise<Response> {
    const url = typeof input === 'string' 
      ? new URL(input, event.url)
      : input instanceof URL 
        ? input 
        : new URL(input.url, event.url);
    
    // 内部APIへのリクエストの場合
    if (url.origin === event.url.origin && url.pathname.startsWith('/api/')) {
      // サーバーサイドでは直接ハンドラを呼び出し
      if (typeof window === 'undefined') {
        const handler = await resolveAPIHandler(url.pathname);
        if (handler) {
          // RequestEventを構築して直接実行
          const apiEvent = createAPIEvent(url, init, event);
          return await handler(apiEvent);
        }
      }
    }
    
    // Cookie転送の自動化
    const headers = new Headers(init?.headers);
    if (!headers.has('cookie') && event.request.headers.has('cookie')) {
      headers.set('cookie', event.request.headers.get('cookie')!);
    }
    
    // 依存関係の自動追跡
    event.depends(url.href);
    
    // 実際のfetch実行
    const response = await globalThis.fetch(input, {
      ...init,
      headers,
      // SSR時はcredentialsを自動設定
      credentials: init?.credentials ?? 'same-origin'
    });
    
    return response;
  };
}
typescript

ミドルウェア統合アーキテクチャ

Hooks(handle、handleFetch、handleError)を通じたミドルウェア統合の内部実装を理解します。

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

Hooksとの統合実装

Hooksは、リクエストをインターセプトしてリクエスト処理のライフサイクルに介入し、認証、ロギング、エラーハンドリングなどのミドルウェア処理を実装できます。

Hooksの基本概念

Hooksは以下のタイミングでリクエスト/レスポンスをインターセプトします。

  • handle: すべてのリクエスト → Load関数実行 → レスポンス
  • handleFetch: Load関数内のfetch呼び出し時
  • handleError: エラー発生時

処理の流れ:リクエスト → [Hooks処理] → Load関数 → [Hooks処理] → レスポンス

// hooks.server.tsの内部処理
// handleフックで使用可能なオプションの型定義
interface ResolveOptions {
  transformPageChunk?: (input: { html: string; done: boolean }) => string;
  filterSerializedResponseHeaders?: (name: string) => boolean;
  preload?: (input: { type: 'font' | 'css' | 'js'; path: string }) => boolean;
}

// sequence関数の内部実装
// 複数のミドルウェアを連鎖的に実行するためのヘルパー関数
// hooks.server.tsでミドルウェアを組み合わせる際に使用します
export function sequence(...handlers: Handle[]): Handle {
  const combined: Handle = handlers.reduce(
    (prev, next) => {
      return async (input, opts) => {
        // 前のハンドラを実行し、その中で次のハンドラを呼び出す
        return prev(input, async (event) => {
          // 次のハンドラに制御を渡す
          return next(
            { ...input, event }, 
            opts
          );
        });
      };
    },
    // 初期ハンドラ(最後に実行される)
    (({ event }, resolve) => resolve(event)) as Handle
  );
  
  return combined;
}

// Load関数実行前のミドルウェア処理
// Hooksで設定されたミドルウェアを通過してからLoad関数が実行される仕組み
async function executeMiddlewareChain(
  event: RequestEvent,
  loadFunctions: LoadFunction[]
): Promise<any> {
  // handle hookを通過
  const transformedEvent = await runHandleHook(event);
  
  // localsに格納されたデータはLoad関数で利用可能
  const results = await Promise.all(
    loadFunctions.map(load => {
      // 各Load関数にtransformedEventを渡す
      return load(transformedEvent);
    })
  );
  
  return mergeLoadResults(results);
}
typescript

並列処理の最適化

Load関数の並列実行フロー

SvelteKitは、レイアウトとページのLoad関数を効率的に並列実行し、待ち時間を最小化します。

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

ウォーターフォール問題と解決策

順次実行(ウォーターフォール)と並列処理の違いを理解することで、パフォーマンスを大幅に改善できます。

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

Load関数の並列実行メカニズム

以下は、SvelteKitが内部的にLoad関数を並列実行する仕組みの概念的な実装です。

// 内部的な並列実行の実装
// Layout ServerLoad、Page ServerLoad、Layout Load、Page Loadを
// 最適な順序で並列実行するためのエグゼキューター
class LoadFunctionExecutor {
  private runningLoads = new Map<string, Promise<any>>();
  
  async executeLoads(
    route: RouteDefinition,
    event: RequestEvent
  ): Promise<Record<string, any>> {
    const loads = this.collectLoadFunctions(route);
    const results: Record<string, any> = {};
    
    // Server LoadとUniversal Loadを分離
    const serverLoads = loads.filter(l => l.type === 'server');
    const universalLoads = loads.filter(l => l.type === 'universal');
    
    // Server Loadを並列実行
    if (serverLoads.length > 0) {
      const serverResults = await this.executeParallel(
        serverLoads,
        event
      );
      Object.assign(results, serverResults);
    }
    
    // Universal LoadにServer Loadの結果を渡して並列実行
    if (universalLoads.length > 0) {
      const universalEvent = {
        ...event,
        data: results  // Server Loadの結果を含む
      };
      
      const universalResults = await this.executeParallel(
        universalLoads,
        universalEvent
      );
      Object.assign(results, universalResults);
    }
    
    return results;
  }
  
  private async executeParallel(
    loads: LoadFunction[],
    event: RequestEvent
  ): Promise<Record<string, any>> {
    // デデュープ: 同じLoad関数の重複実行を防ぐ
    const promises = loads.map(load => {
      const key = load.id;
      
      if (!this.runningLoads.has(key)) {
        this.runningLoads.set(
          key,
          this.executeLoad(load, event)
            .finally(() => this.runningLoads.delete(key))
        );
      }
      
      return this.runningLoads.get(key)!;
    });
    
    const results = await Promise.all(promises);
    
    // 結果をオブジェクトにマージ
    return loads.reduce((acc, load, index) => {
      acc[load.name] = results[index];
      return acc;
    }, {} as Record<string, any>);
  }
  
  private async executeLoad(
    load: LoadFunction,
    event: RequestEvent
  ): Promise<any> {
    try {
      // タイムアウト設定
      const timeout = load.timeout || 30000;
      const result = await Promise.race([
        load.fn(event),
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('Load timeout')), timeout)
        )
      ]);
      
      return result;
    } catch (error) {
      // エラーハンドリング
      if (load.optional) {
        return null;  // オプショナルな場合はnullを返す
      }
      throw error;  // 必須の場合はエラーを伝播
    }
  }
}
typescript

ストリーミングSSRの内部実装

ストリーミングSSRの処理フロー

ストリーミングSSRでは、重要なコンテンツを即座に送信し、時間のかかるデータは後から順次送信します。

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

通常のSSRとストリーミングSSRの比較

ストリーミングSSRは、ユーザー体験を大幅に改善します。

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

ストリーミングレスポンスの生成

以下は、ストリーミングSSRの内部実装の概念です。

// ストリーミングSSRの内部実装
// ReadableStreamを使用して、データをチャンク単位で順次送信する仕組み
class StreamingRenderer {
  async renderStreaming(
    component: Component,
    props: Props,
    loadPromises: Map<string, Promise<any>>
  ): Promise<ReadableStream> {
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();
    
    return new ReadableStream({
      async start(controller) {
        // 初期HTMLを即座に送信
        const initialHTML = await renderInitialHTML(component, props);
        controller.enqueue(encoder.encode(initialHTML));
        
        // ストリーミングデータのプレースホルダーを挿入
        controller.enqueue(encoder.encode(
          '<' + 'script id="__SVELTE_KIT_STREAMING__" type="application/json">'
        ));
        
        // Promiseが解決されるたびにデータを送信
        for (const [key, promise] of loadPromises) {
          try {
            const data = await promise;
            const chunk = JSON.stringify({ key, data });
            controller.enqueue(encoder.encode(chunk + '\n'));
            
            // クライアントサイドでの即座の更新トリガー
            controller.enqueue(encoder.encode(
              `<${'script'}>__sveltekit_stream_update(${JSON.stringify(key)})</${'script'}>`
            ));
          } catch (error) {
            // エラーもストリーミング
            const errorChunk = JSON.stringify({ 
              key, 
              error: error.message 
            });
            controller.enqueue(encoder.encode(errorChunk + '\n'));
          }
        }
        
        // ストリーミング完了マーカー
        controller.enqueue(encoder.encode('</' + 'script>'));
        controller.close();
      }
    });
  }
}
typescript

パフォーマンス計測と最適化

内部的なパフォーマンス計測

SvelteKitは内部的にLoad関数の実行時間を計測し、開発環境ではパフォーマンス問題を警告します。以下は、その仕組みの概念的な実装です。

// Load関数のパフォーマンス計測
// 実行時間、依存関係、キャッシュヒット率などを追跡し、
// Server-Timingヘッダーとしてブラウザの開発者ツールに表示できます
interface LoadMetrics {
  name: string;
  startTime: number;
  endTime: number;
  duration: number;
  dependencies: string[];
  cacheHit: boolean;
  error?: Error;
}

class PerformanceMonitor {
  private metrics: LoadMetrics[] = [];
  
  async measureLoad<T>(
    name: string,
    fn: () => Promise<T>,
    deps: string[]
  ): Promise<T> {
    const startTime = performance.now();
    const metric: LoadMetrics = {
      name,
      startTime,
      endTime: 0,
      duration: 0,
      dependencies: deps,
      cacheHit: false
    };
    
    try {
      const result = await fn();
      metric.endTime = performance.now();
      metric.duration = metric.endTime - metric.startTime;
      
      // 開発環境では警告を出力
      if (import.meta.env.DEV && metric.duration > 1000) {
        console.warn(
          `Slow Load function: ${name} took ${metric.duration}ms`
        );
      }
      
      this.metrics.push(metric);
      return result;
    } catch (error) {
      metric.error = error as Error;
      metric.endTime = performance.now();
      metric.duration = metric.endTime - metric.startTime;
      this.metrics.push(metric);
      throw error;
    }
  }
  
  getMetrics(): LoadMetrics[] {
    return this.metrics;
  }
  
  // Server-Timingヘッダーの生成
  generateServerTiming(): string {
    return this.metrics
      .map(m => `${m.name};dur=${m.duration.toFixed(2)}`)
      .join(', ');
  }
}
typescript

メモリ管理とガベージコレクション

キャッシュのメモリ管理

キャッシュがメモリを圧迫しないよう、WeakRefとFinalizationRegistryを使用した高度なメモリ管理が行われています。

// メモリ効率的なキャッシュ管理
// WeakRefを使用してガベージコレクションを妨げずにキャッシュを管理
// メモリが逸迫した場合は自動的にキャッシュが解放されます
class MemoryEfficientCache {
  private cache = new Map<string, WeakRef<any>>();
  private registry = new FinalizationRegistry((key: string) => {
    // オブジェクトがGCされたらキャッシュからも削除
    this.cache.delete(key);
  });
  
  set(key: string, value: any) {
    // WeakRefを使用してメモリリークを防ぐ
    const ref = new WeakRef(value);
    this.cache.set(key, ref);
    
    // GC時のクリーンアップを登録
    this.registry.register(value, key);
  }
  
  get(key: string): any | null {
    const ref = this.cache.get(key);
    if (!ref) return null;
    
    const value = ref.deref();
    if (value === undefined) {
      // オブジェクトがGCされていた場合
      this.cache.delete(key);
      return null;
    }
    
    return value;
  }
  
  // 定期的なクリーンアップ
  cleanup() {
    for (const [key, ref] of this.cache) {
      if (ref.deref() === undefined) {
        this.cache.delete(key);
      }
    }
  }
}
typescript

デバッグとトラブルシューティング

Load関数のデバッグツール

開発中のLoad関数の動作を追跡するためのデバッグユーティリティです。パラメータ、実行時間、結果をコンソールに詳細に出力します。

// デバッグ用のLoad関数ラッパー
// 開発環境でのみ動作し、Load関数の入出力を詳細にログ出力します
function debugLoadFunction(name: string, load: LoadFunction): LoadFunction {
  return async (event) => {
    if (import.meta.env.DEV) {
      console.group(`🔄 Load: ${name}`);
      console.log('URL:', event.url.toString());
      console.log('Params:', event.params);
      console.log('Locals:', event.locals);
      console.time(`Load: ${name}`);
    }
    
    try {
      const result = await load(event);
      
      if (import.meta.env.DEV) {
        console.log('Result:', result);
        console.timeEnd(`Load: ${name}`);
        console.groupEnd();
      }
      
      return result;
    } catch (error) {
      if (import.meta.env.DEV) {
        console.error('Error:', error);
        console.timeEnd(`Load: ${name}`);
        console.groupEnd();
      }
      throw error;
    }
  };
}

// 使用例
// +page.tsや+layout.tsでデバッグラッパーを使用することで、
// Load関数の動作を詳細に追跡できます
export const load = debugLoadFunction('products', async ({ fetch }) => {
  const response = await fetch('/api/products');
  return {
    products: await response.json()
  };
});
typescript

まとめ

SvelteKitのデータロードアーキテクチャは、以下の要素で構成されています。

サーバー・クライアント共通

  • RequestEventオブジェクト: リクエスト情報の統一的な管理
  • 並列処理: Load関数の最適な実行順序
  • ミドルウェア統合: Hooksを通じた拡張性

クライアントサイド特有

  • キャッシュメカニズム: ブラウザメモリでのデータ再利用
  • invalidate機能: キャッシュの手動無効化と再取得
  • メモリ管理: WeakRefによる効率的なリソース利用

サーバーサイド特有

  • ストリーミングSSR: プログレッシブな表示の実現
  • ステートレス処理: リクエストごとに独立した実行

これらの内部実装を理解することで、より効率的なアプリケーション開発が可能になります。

次のステップ

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