kouno.log

    で移動 · Enter で開く

    Fresh 2 のハンドラーからページコンポーネントへのデータの渡し方

    Fresh 2 (@fresh/core@^2.2.2) でハンドラーからページコンポーネントにデータを渡す方法がドキュメントに明記されておらず、ソースを読んで初めて分かったのでメモ。

    ##結論

    "fresh" からエクスポートされている page() ヘルパーを使うのが正しい方法。

    // ❌ 動かない
    export const handlers = define.handlers({
      async GET(ctx) {
        const tasks = await listTasks();
        return { tasks }; // data プロップが undefined になる
      },
    });
    
    // ✅ 正しい(ドキュメント記載の方法)
    import { page } from "fresh";
    
    export const handlers = define.handlers({
      async GET(ctx) {
        const tasks = await listTasks();
        return page({ tasks }); // ← page() でラップする
      },
    });
    
    export default define.page<typeof handlers>(function Page({ data }) {
      const tasks = data?.tasks ?? [];
      // ...
    });

    page() の第2引数でレスポンスのカスタマイズもできる:

    return page(data, {
      status: 201,
      headers: { "Cache-Control": "public, max-age=3600" },
    });

    ##なぜそうなるのか

    page() ヘルパーが { data: ..., status: ..., headers: ... } という構造のオブジェクトを生成し、Fresh 2 の内部処理がそれを読み取る。

    Fresh 2 の src/segments.tsrenderRoute 関数:

    // https://jsr.io/@fresh/core/2.2.2/src/segments.ts
    const res = await fn(ctx); // ハンドラーを呼ぶ
    
    if (res instanceof Response) {
      return res; // Response なら即返す
    }
    
    // ...(status や headers の処理)
    
    // ページコンポーネントに渡す props は res.data
    const result = await renderRouteComponent(ctx, {
      component: route.component,
      props: res.data as any, // ← ここ
    }, () => null);

    ページコンポーネントの data prop として渡されるのは res.data だけ。

    page({ tasks }){ data: { tasks } } を返すので正しく渡る。
    return { tasks } と書いた場合、res.dataundefined になるため何も渡されない。

    ##デバッグの手がかり

    return を省略(= undefined を返す)した場合は次のエラーが出る。

    TypeError: Cannot read properties of undefined (reading 'status')
        at renderRoute (https://jsr.io/@fresh/core/2.2.2/src/segments.ts:151)

    renderRouteres.status を読もうとして undefined でクラッシュする。このエラーを手がかりにソースを読むと page() の仕様に気づける。

    ##戻り値の完全な型イメージ

    // GET ハンドラーが返せる形
    type HandlerResult<Data> =
      | Response           // リダイレクト・エラーなど
      | {
          data?: Data;     // ページコンポーネントの data prop(page() が生成)
          status?: number; // HTTP ステータス(省略時 200)
          headers?: HeadersInit;
        };

    ##環境

    • @fresh/core: 2.2.2
    • @fresh/plugin-vite: 1.0.8
    • Deno: 2.7.11