シングルフェッチ
シングルフェッチは、新しいデータローディング戦略とストリーミング形式です。シングルフェッチを有効にすると、Remixはクライアント側遷移でサーバーに対して1回のHTTP呼び出しのみを行います。以前は、ローダーごとに複数のHTTP呼び出しが並行して行われていました。さらに、シングルフェッチでは、loader
とaction
からDate
、Error
、Promise
、RegExp
など、生のオブジェクトを送信することもできます。
概要
Remixは、v2.9.0
(後にv2.13.0
でfuture.v3_singleFetch
として安定化)のfuture.unstable_singleFetch
フラグの背後にある"シングルフェッチ"(RFC)のサポートを導入しました。これにより、この動作を選択できます。シングルフェッチは、React Router v7でデフォルトになります。
シングルフェッチを有効にすることは、事前に労力が少なく、その後、すべての破壊的変更を徐々に採用できます。シングルフェッチを有効にするために必要な最小限の変更を適用してから、移行ガイドを使用してアプリケーションの増分変更を行い、React Router v7へのスムーズな、非破壊的なアップグレードを確保できます。
また、破壊的変更も確認して、特にシリアル化とステータス/ヘッダーの動作に関する基礎となる動作変更について理解しておきましょう。
シングルフェッチの有効化
1. futureフラグを有効にする
2. fetch
ポリフィルの廃止
シングルフェッチでは、@remix-run/web-fetch
ポリフィルにはないAPIに依存するため、fetch
ポリフィルとしてundici
を使用するか、Node 20+の組み込みfetch
を使用する必要があります。詳細については、以下の2.9.0リリースノートのUndiciセクションを参照してください。
-
Node 20+を使用している場合は、
installGlobals()
への呼び出しをすべて削除し、Nodeの組み込みfetch
を使用してください(これはundici
と同じです)。 -
独自のサーバーを管理していて
installGlobals()
を呼び出している場合は、undici
を使用するためにinstallGlobals({ nativeFetch: true })
を呼び出す必要があります。 -
remix-serve
を使用している場合は、シングルフェッチが有効になっていると自動的にundici
を使用します。 -
miniflare/cloudflare workerでremixプロジェクトを使用している場合は、互換性フラグが
2023-03-01
以降に設定されていることを確認してください。
3. headers
の実装を調整する(必要に応じて)
シングルフェッチが有効になっていると、クライアント側ナビゲーションでは、複数のローダーを実行する場合でも、1回の要求のみが行われます。呼び出されるハンドラーのヘッダーのマージを処理するために、headers
エクスポートは、loader
/action
のデータ要求にも適用されるようになりました。多くの場合、ドキュメント要求にすでに存在するロジックは、新しいシングルフェッチデータ要求にほぼ十分です。
4. <RemixServer>
にnonce
を追加する(CSPを使用している場合)
<RemixServer>
コンポーネントは、クライアント側でストリーミングデータを処理するインラインスクリプトをレンダリングします。スクリプトのコンテンツセキュリティポリシーがnonce-sourcesを使用している場合は、<RemixServer nonce>
を使用してnonceをこれらの<script>
タグに渡すことができます。
5. renderToString
を置き換える(使用している場合)
ほとんどのRemixアプリでは、renderToString
を使用している可能性は低いですが、entry.server.tsx
で使用するように選択した場合、読み続けてください。そうでない場合は、この手順をスキップできます。
ドキュメント要求とデータ要求の間の一貫性を維持するために、turbo-stream
は、初期ドキュメント要求でのデータの送信にも使用される形式です。つまり、シングルフェッチを選択すると、アプリケーションではrenderToString
を使用できなくなり、entry.server.tsx
でrenderToPipeableStream
またはrenderToReadableStream
)などのReactストリーミングレンダラーAPIを使用する必要があります。
これは、HTTPレスポンスをストリーミングする必要があるという意味ではありません。renderToPipeableStream
のonAllReady
オプションまたはrenderToReadableStream
のallReady
プロミスを利用することで、引き続きドキュメント全体を一度に送信できます。
クライアント側では、ストリーミングされたデータはSuspense
境界でラップされて配信されるため、クライアント側のhydrateRoot
呼び出しをstartTransition
呼び出しでラップする必要があります。
破壊的変更
シングルフェッチでは、いくつかの破壊的変更が導入されています。その中には、フラグを有効にしたときにすぐに処理する必要があるものと、フラグを有効にした後に段階的に処理できるものがあります。次のメジャーバージョンにアップグレードする前に、これらの変更がすべて処理されていることを確認する必要があります。
事前に処理する必要がある変更:
fetch
ポリフィルの廃止: 古いinstallGlobals()
ポリフィルはシングルフェッチでは機能しません。undiciベースのポリフィルを取得するには、ネイティブのNode 20fetch
APIを使用するか、カスタムサーバーでinstallGlobals({ nativeFetch: true })
を呼び出す必要があります。headers
エクスポートがデータ要求に適用される:headers
関数は、ドキュメント要求とデータ要求の両方に適用されるようになりました。
処理が必要になる可能性のある、認識しておく必要がある変更:
- 新しいストリーミングデータ形式: シングルフェッチは、
turbo-stream
を介して新しいストリーミング形式を内部で使用しています。これは、JSONよりも複雑なデータをストリーミングできるようになったことを意味します。 - 自動シリアル化なし:
loader
とaction
関数が返す生のオブジェクトは、もはや自動的にJSONResponse
に変換されず、ワイヤー上でそのままシリアル化されます。 - 型推論の更新: 最も正確な型推論を得るには、
unstable_singleFetch: true
を使用してRemixのFuture
インターフェースを拡張する必要があります。 - GETナビゲーションでのデフォルトの再検証動作がオプトアウトに変更される: 通常のナビゲーションでのデフォルトの再検証動作は、オプトインからオプトアウトに変更され、サーバーのローダーはデフォルトで再実行されます。
action
の再検証をオプトインにする:action
の4xx
/5xx
Response
後の再検証は、オプトアウトではなく、オプトインになりました。
シングルフェッチを使用した新しいルートの追加
シングルフェッチが有効になっていると、より強力なストリーミング形式を利用したルートを作成できます。
unstable_singleFetch: true
を使用してRemixのFuture
インターフェースを拡張する必要があります。詳細については、型推論セクションを参照してください。
シングルフェッチでは、BigInt
、Date
、Error
、Map
、Promise
、RegExp
、Set
、Symbol
、URL
などのデータ型をローダーから返すことができます。
シングルフェッチを使用したルートの移行
現在、ローダーからResponse
インスタンス(つまり、json
/defer
)を返している場合は、シングルフェッチを利用するためにアプリコードを変更する必要はありません。
ただし、将来のReact Router v7へのアップグレードをより適切に準備するために、ルートごとに以下の変更を始めることをお勧めします。これは、ヘッダーとデータ型を更新しても何も壊れないことを検証する最も簡単な方法です。
型推論
シングルフェッチを使用していない場合は、loader
またはaction
から返されたプレーンなJavaScriptオブジェクトは、自動的にJSONレスポンスにシリアル化されます(json
で返された場合と同様)。型推論はこれが事実であると想定し、生のオブジェクトの返値を、JSONシリアル化されたものとして推論します。
シングルフェッチでは、生のオブジェクトは直接ストリーミングされるため、シングルフェッチを選択すると、組み込みの型推論はもはや正確ではありません。たとえば、Date
はクライアントで文字列にシリアル化されると想定されます😕。
シングルフェッチ型の有効化
シングルフェッチ型に切り替えるには、v3_singleFetch: true
を使用してRemixのFuture
インターフェースを拡張する必要があります。
これは、tsconfig.json
> include
でカバーされている任意のファイルで行うことができます。
Remixプラグインのfuture.v3_singleFetch
futureフラグと共存させるために、vite.config.ts
で行うことをお勧めします。
これで、useLoaderData
、useActionData
、およびtypeof loader
ジェネリックを使用するその他のユーティリティは、シングルフェッチ型を使用するようになりました。
関数とクラスのインスタンス
一般的に、関数はネットワークを介して確実に送信できないため、undefined
としてシリアル化されます。
メソッドもシリアル化できないため、クラスのインスタンスはシリアル化可能なプロパティのみに絞られます。
clientLoader
とclientAction
clientLoader
引数とclientAction
引数の型を含めることを確認してください。これは、型がクライアントデータ関数を検出する方法です。
クライアント側のローダーとアクションからのデータは決してシリアル化されないため、それらの型は保持されます。
ヘッダー
headers
関数は、シングルフェッチが有効になっている場合、ドキュメント要求とデータ要求の両方で使用されるようになりました。この関数を使用して、並行して実行されるローダーから返されたヘッダーをマージするか、特定のactionHeaders
を返してください。
返されるレスポンス
シングルフェッチでは、Response
インスタンスを返す必要がなくなり、生のオブジェクトの返値を介してデータを直接返すことができます。そのため、シングルフェッチを使用する場合は、json
/defer
ユーティリティは廃止されたと見なしてください。これらは、v2の間は残っているため、すぐに削除する必要はありません。これらは、次のメジャーバージョンで削除される可能性が高いため、今後は段階的に削除することをお勧めします。
v2では、引き続き通常のResponse
インスタンスを返し、status
/headers
はドキュメント要求の場合と同じように適用されます(headers()
関数を介してヘッダーをマージします)。
時間とともに、ローダーとアクションから返されるレスポンスを段階的に排除する必要があります。
loader
/action
がstatus
/headers
を設定せずにjson
/defer
を返していた場合は、json
/defer
への呼び出しを削除し、データを直接返すことができます。loader
/action
がjson
/defer
を介してカスタムstatus
/headers
を返していた場合は、新しいdata()
ユーティリティを使用するように切り替える必要があります。
クライアントローダー
アプリにclientLoader
関数を使用するルートがある場合、シングルフェッチの動作がわずかに変更されることに注意することが重要です。clientLoader
は、サーバーのloader
関数の呼び出しをオプトアウトするための方法を提供することを目的としているため、シングルフェッチ呼び出しでそのサーバーのローダーを実行することは不適切です。しかし、すべてのローダーは並行して実行され、実際にサーバーデータを求めているclientLoader
がどれであるかを知るまで、呼び出しを待つことはできません。
たとえば、次の/a/b/c
ルートを考えてみましょう。
ユーザーが/ -> /a/b/c
に移動した場合、a
とb
のサーバーローダーと、c
のclientLoader
を実行する必要があります。これは、最終的に(またはそうでない場合)、独自のサーバーloader
を呼び出す可能性があります。a
/b
のloader
を取得する必要があるときに、シングルフェッチ呼び出しにc
のサーバーloader
を含めるかどうかを判断することはできません。また、c
が実際にserverLoader
を呼び出す(または返す)まで遅らせることもできません。ウォーターフォールが発生するためです。
そのため、clientLoader
をエクスポートして、そのルートがシングルフェッチをオプトアウトした場合、serverLoader
を呼び出すと、そのルートのサーバーloader
のみを取得するための単一のフェッチが行われます。clientLoader
をエクスポートしていないすべてのルートは、単一のHTTP要求でフェッチされます。
したがって、上記のルート設定では、/ -> /a/b/c
へのナビゲーションにより、最初にa
とb
のルートに対して単一のシングルフェッチ呼び出しが行われます。
GET /a/b/c.data?_routes=routes/a,routes/b
その後、c
がserverLoader
を呼び出すと、c
のサーバーloader
のみに対する独自の呼び出しが行われます。
GET /a/b/c.data?_routes=routes/c
リソースルート
シングルフェッチで使用される新しいストリーミング形式のため、loader
とaction
関数から返された生のJavaScriptオブジェクトは、json()
ユーティリティを使用してResponse
インスタンスに自動的に変換されなくなりました。代わりに、ナビゲーションデータロードでは、他のローダーデータと結合され、turbo-stream
レスポンスでストリーミングされます。
これは、個別にアクセスされることを目的とした、リソースルートにとって興味深いジレンマをもたらします。これは、常にRemix APIを介してアクセスされるわけではなく、他のHTTPクライアント(fetch
、cURL
など)からもアクセスできます。
リソースルートが内部のRemix APIでの消費を目的としている場合、より複雑な構造(Date
やPromise
インスタンスなど)をストリーミングダウンする機能を解放するために、turbo-stream
エンコーディングを活用したいと考えています。ただし、外部からアクセスされる場合は、より簡単に消費できるJSON構造を返すことを好むでしょう。したがって、v2で生のオブジェクトを返した場合、動作はわずかに曖昧です。turbo-stream
またはjson()
でシリアル化する必要がありますか?
後方互換性を容易にし、シングルフェッチfutureフラグの採用を容易にするために、Remix v2は、これがRemix APIからアクセスされているか、外部からアクセスされているかによって処理します。将来的には、Remixでは、生のオブジェクトを外部から消費するためにストリーミングダウンしない場合は、独自のJSONレスポンスを返す必要があります。
シングルフェッチが有効になっているRemix v2の動作は次のとおりです。
-
useFetcher
などのRemix APIからアクセスする場合、生のJavaScriptオブジェクトは、通常のローダーとアクションと同様にturbo-stream
レスポンスとして返されます(これは、useFetcher
がリクエストに.data
サフィックスを追加するためです)。 -
fetch
やcURL
などの外部ツールからアクセスする場合、v2では後方互換性のために、json()
への自動変換を継続します。- Remixは、この状況が発生すると、廃止警告をログ出力します。
- 適宜、影響を受けるリソースルートハンドラーを更新して、
Response
オブジェクトを返すことができます。 - これらの廃止警告に対処することで、最終的なRemix v3へのアップグレードをより適切に準備できます。
詳細
ストリーミングデータ形式
以前、RemixはJSON.stringify
を使用してローダー/アクションデータをワイヤー上でシリアル化し、defer
レスポンスをサポートするためにカスタムストリーミング形式を実装する必要がありました。
シングルフェッチでは、Remixは内部でturbo-stream
を使用するようになりました。これにより、ストリーミングが第一級のサポートされ、JSONよりも複雑なデータを自動的にシリアル化/デシリアル化できます。BigInt
、Date
、Error
、Map
、Promise
、RegExp
、Set
、Symbol
、URL
などのデータ型は、turbo-stream
を介して直接ストリーミングダウンできます。Error
のサブタイプも、クライアントでグローバルに利用可能なコンストラクターがある場合はサポートされます(SyntaxError
、TypeError
など)。
シングルフェッチを有効にした後、コードを変更する必要があるかどうかは、状況によって異なります。
- ✅
loader
/action
関数から返されたjson
レスポンスは、引き続きJSON.stringify
を介してシリアル化されます。そのため、Date
を返した場合、useLoaderData
/useActionData
からはstring
を受け取ります。 - ⚠️
defer
インスタンスまたは生のオブジェクトを返した場合、turbo-stream
を介してシリアル化されるようになりました。そのため、Date
を返した場合、useLoaderData
/useActionData
からはDate
を受け取ります。- 現状の動作を維持する場合は(ストリーミング
defer
レスポンスを除く)、既存の生のオブジェクトの返値をjson
でラップすればよいでしょう。
- 現状の動作を維持する場合は(ストリーミング
これは、ワイヤー上でPromise
インスタンスを送信するためにdefer
ユーティリティを使用する必要がなくなったことも意味します!生のオブジェクトにPromise
を含めることができ、useLoaderData().whatever
で取得できます。必要に応じてPromise
をネストすることもできますが、潜在的なUXへの影響に注意してください。
シングルフェッチを採用したら、アプリケーション全体でjson
/defer
の使用を段階的に削除し、生のオブジェクトを返すようにすることをお勧めします。
ストリーミングタイムアウト
以前、Remixはデフォルトのentry.server.tsx
ファイルに組み込まれたABORT_TIMEOUT
という概念を持っていましたが、これはReactレンダラーを終了させるだけで、保留中の遅延プロミスのクリーンアップは何も行いませんでした。
Remixは内部でストリーミングされるようになったため、turbo-stream
処理をキャンセルし、保留中のプロミスを自動的に拒否して、それらのエラーをクライアントにストリーミングアップできます。デフォルトでは、これは4950ミリ秒後に発生します。これは、ほとんどのentry.server.tsx
ファイルで現在の5000ミリ秒のABORT_DELAY
をわずかに下回る値です。これは、プロミスをキャンセルして、Reactレンダラーが終了する前に、React側の処理に拒否をストリーミングアップする必要があるためです。
これは、entry.server.tsx
から数値のstreamTimeout
をエクスポートすることで制御できます。Remixは、この値を、loader
/action
の保留中のプロミスを拒否するミリ秒数として使用します。この値は、Reactレンダラーを中止するタイムアウトと切り離すことをお勧めします。また、常にReactのタイムアウトをより高い値に設定して、streamTimeout
の基礎となる拒否をストリーミングダウンする時間を与える必要があります。
再検証
通常のナビゲーション動作
よりシンプルな精神モデルと、ドキュメント要求とデータ要求の整合性に加えて、シングルフェッチのもう1つの利点は、よりシンプルで(おそらくは)優れたキャッシュ動作です。一般的に、シングルフェッチでは、以前の複数フェッチ動作と比較して、HTTP要求が少なくなり、その結果をより頻繁にキャッシュできる可能性があります。
キャッシュの断片化を減らすために、シングルフェッチでは、GETナビゲーションでのデフォルトの再検証動作が変更されました。以前は、Remixは、shouldRevalidate
を介してオプトインしない限り、再利用された祖先ルートのローダーを再実行しませんでした。現在、Remixは、GET /a/b/c.data
などのシングルフェッチ要求の単純なケースでは、デフォルトでそれらを再実行します。shouldRevalidate
またはclientLoader
関数が存在しない場合、これがアプリの動作になります。
shouldRevalidate
またはclientLoader
のいずれかをアクティブなルートに追加すると、実行されるルートのサブセットを指定する_routes
パラメーターを含む、粒度の細かいシングルフェッチ呼び出しがトリガーされます。
clientLoader
が内部でserverLoader()
を呼び出すと、その特定のルートに対する別のHTTP呼び出しが、以前の動作と同様にトリガーされます。
たとえば、/a/b
にいて/a/b/c
に移動した場合:
shouldRevalidate
またはclientLoader
関数が存在しない場合:GET /a/b/c.data
- すべてのルートにローダーがあるが、
routes/a
がshouldRevalidate
でオプトアウトしている場合:GET /a/b/c.data?_routes=root,routes/b,routes/c
- すべてのルートにローダーがあるが、
routes/b
にclientLoader
がある場合:GET /a/b/c.data?_routes=root,routes/a,routes/c
- Bの
clientLoader
がserverLoader()
を呼び出すと:GET /a/b/c.data?_routes=routes/b
この新しい動作がアプリケーションに適していない場合は、親ルートにshouldRevalidate
を追加して、false
を返し、必要なシナリオで古い動作をオプトインすることができます。
別のオプションとして、高価な親ローダー計算のためにサーバー側のキャッシュを活用できます。
サブミットの再検証動作
以前は、Remixは、アクションのサブミット後、アクションの結果に関係なく、常にすべてのアクティブなローダーを再検証していました。shouldRevalidate
を使用して、ルートごとに再検証をオプトアウトできます。
シングルフェッチでは、action
が4xx/5xx
ステータスコードを含むResponse
を返したり、スローしたりした場合、Remixはデフォルトでローダーを再検証しません。action
が4xx/5xx
Responseではないものを返したり、スローしたりした場合、再検証動作は変わりません。これは、ほとんどの場合、4xx
/5xx
Responseを返す場合は、実際にはデータを変更していないため、データを再読み込みする必要がないためです。
4xx/5xx
アクションレスポンス後にローダーを1つ以上再検証する必要がある場合は、shouldRevalidate
関数からtrue
を返すことで、ルートごとに再検証をオプトインできます。アクションのステータスコードに基づいて判断する必要がある場合は、actionStatus
という新しいパラメーターが関数に渡されます。
再検証は、シングルフェッチHTTP呼び出しの?_routes
クエリ文字列パラメーターを介して処理されます。これにより、呼び出されるローダーが制限されます。つまり、粒度の細かい再検証を行っている場合、要求されているルートに基づいてキャッシュ列挙が行われます。しかし、すべての情報はURLにあるため、特別なCDN設定は必要ありません(これが、CDNがVary
ヘッダーを尊重することを要求するカスタムヘッダーを介して行われた場合とは対照的です)。