>_tech-draft
Vercelのアイコン
Vercel
動画公開日
タイトル

Reactive state for the backend

再生時間

14分 14秒

Next.js App Routerにおける`use cache`徹底解説:進化するキャッシングとリアクティブなデータ管理

ポイント

  • Next.js App Routerでのキャッシングとリアクティブなデータ管理に課題を感じている開発者向けに、`use cache`の活用法を解説します。
  • Pages Routerでの煩雑だったキャッシュ無効化に対し、App Routerの`use cache`はフレームワークがデータ依存性を認識し、タグベースで効率的な再検証を実現します。
  • `use cache`を導入することで、開発者はキャッシングロジックを簡素化し、`revalidateTag`を一度呼ぶだけで常に最新のコンテンツを提供できる開発体験が得られます。

Next.js App Routerにおけるuse cache徹底解説:進化するキャッシングとリアクティブなデータ管理ウォレット

導入:リアクティブな状態とキャッシングの課題

ウェブアプリケーション開発において、「リアクティブな状態(Reactive State)」とは、データに変更が加えられた際に、その変更が即座にユーザーインターフェースに反映される概念を指します。これにより、ユーザーは常に最新の情報を目にすることができ、古いデータ(stale data)に遭遇するリスクが低減されます。

例えば、あるブラウザタブで設定を切り替えたにもかかわらず、別の開いているタブにはその変更が反映されていない、といった経験はありませんでしょうか。これはリアクティブな状態が適切に管理されていない典型的な例です。チームメンバーが同時に状態を更新しているにもかかわらず、ページがそれを反映しないというシナリオも同様です。コメントを残したのに、それが即座に表示されない、といった状況もユーザー体験を損ねます。

フロントエンド開発では、WebSocketやSSE(Server-Sent Events)などのツールを用いてこの問題を解決することが可能です。クライアント側でデータ変更をリッスンし、状態を更新することで、リアルタイムでの同期を実現します。Vercelの製品であるv0では、メッセージを送信すると、複数のブラウザクライアント間でリアルタイムに更新されます。

しかし、サーバーサイドレンダリング(SSR)を行う分散型またはサーバーレス環境では、永続的なクライアント接続を確立することが難しくなります。これにより、データキャッシュやCDNキャッシュなど、複数のキャッシュロケーションを管理する必要が生じ、状態管理が複雑化します。

私はこれまで、Next.jsで非常に高速で大規模なサイト構築に携わってきました。例えば、100万点以上の商品を扱い、瞬時のページナビゲーションを実現するデモECサイト「Next.js Faster」や、200万ページ以上を持つ「Answer Overflow」、リアルタイムのドメイン検索サービス「Vercel Domains」などです。これらのサイトに共通しているのは、キャッシングを非常に extensively に活用している点です。キャッシングはデータをユーザーに近づけ、パフォーマンスを向上させるために不可欠です。

では、キャッシングがそれほど優れているのであれば、なぜすべてのサイトが高速ではないのでしょうか?その理由は、キャッシュされたデータが使用されているすべての場所を把握し、それらを再検証(revalidate)してデータが古くならないようにする必要があるからです。Next.js FasterやAnswer Overflowは、多くのページを高速に提供する点では優れていますが、データ変更へのリアクティブ性は高くありませんでした。

Next.jsにおけるキャッシングの進化

Next.jsにおけるキャッシングの課題と進化を、Pages RouterからApp Routerへの移行という観点から見ていきましょう。

Pages Routerのキャッシングと課題

Pages Routerでは、主に2つのキャッシングオプションがありました。

  1. 手動キャッシング: Redisなどのデータストアに手動でデータを書き込む方法です。これは、デプロイ間でAPI呼び出しの結果をキャッシュしたり、メモリ内に保持したい場合に利用されました。
  2. ISR(Incremental Static Regeneration): getStaticProps または getServerSideProps 関数と revalidate オプションを組み合わせることで、ページをISRキャッシュに書き込むことができました。これは、エッジキャッシュやCDNキャッシュからページを提供したい場合に利用されました。

しかし、Pages Routerにおけるキャッシュ無効化は非常に手間のかかる作業でした。データが更新された際、開発者はルートハンドラーを呼び出し、そのデータが使用されている可能性のあるすべてのパスを特定し、それぞれのパスに対して revalidate を手動で呼び出す必要がありました。これは、データがどこで使われているかを追跡し、手動で revalidate を何度も実行するという、多くの作業を伴いました。

なぜこれほど多くの revalidate 呼び出しが必要だったのでしょうか。Pages Routerでは、フレームワークの観点から見ると、ルートマップはレンダリングされたページに対応していましたが、そのページのレンダリングにどのようなデータが使われたかをフレームワークは知りませんでした。例えば、/posts/:id というルートが getContentgetViewsgetUsers といった複数の関数を呼び出していても、フレームワークはそのマッピングやページが依存するデータを把握していなかったのです。フレームワークが知っていたのは、ルートが特定のページをレンダリングするということだけでした。

App Routerの登場とuse cache

App Routerの導入により、フレームワークがキャッシングを理解し、データキャッシングのためのプリミティブ(基本的な機能)を提供するというコンセプトがもたらされました。当初は unstable_cachefetch キャッシングのオプションがありましたが、これらのAPIは使い勝手が悪く、機能も限定的でした。例えば、unstable_cache では、API呼び出しの結果に基づいてキャッシュタグを設定することができませんでした。

しかし、Next.jsは急速に進化し、use cache が登場しました。これは、Reactコンポーネントとデータの両方をキャッシュできる、非常に優れた共通のインターフェースです。

なぜフレームワークがキャッシングに関心を持つのか?

use cache やその他のキャッシングプリミティブが導入されたとき、あなたは「なぜフレームワークが私のデータキャッシング方法に関心を持つのか?」と疑問に思うかもしれません。その答えは、きめ細やかなリアクティビティとデータのキャッシングにあります。

App Routerでは、特定のルートを構成するデータが何かをフレームワークが正確に把握しています。これにより、フレームワークは適切なキャッシュヘッダーで応答し、下流のキャッシュ(CDNなど)が変更に対してきめ細やかな再検証(granular revalidation)を実行できるようになります。

App Routerでは、/posts/:id のパスが getContentgetViewsgetUsers を呼び出してページをレンダリングする際に、ページがこれらの関数が使われたことを認識します。さらに、cache tags を通じて返された結果に関するメタデータを提供することで、関数が呼び出されただけでなく、ページが特定の返されたデータに依存していることもフレームワークが把握するのです。

無効化の劇的な改善

Pages Routerでの無効化と比較すると、App Routerでは、データが使用されているすべての場所に対してrevalidateTagを一度呼び出すだけで済みます。しかも、これはページを再検証するだけでなく、Redisのようなデータキャッシュにデータを保存している場合でも、同じ revalidateTag 呼び出しがそのキャッシュ場所にも適用されます。

フレームワークがキャッシングとトラッキングを処理してくれるため、開発者が revalidate する場所を手動で指定するといった作業は不要になります。

use cacheによる開発体験の向上

従来のRedisを使った手動キャッシュから unstable_cache、そして最終的に use cache へと進化する中で、use cacheがもたらす開発者体験(DX: Developer Experience)の向上は非常に明確です。

use cacheを使用することで、関数のフローにキャッシングのための条件付きロジックを追加する必要がなくなります。また、より使いやすいAPIを提供するだけでなく、Pages Routerでは不可能だった機能、つまりきめ細やかな無効化といった機能を可能にしています。

use cacheの実践的な活用法

use cacheの最も素晴らしい点は、コードベースにおいて use cacheについて意識することが非常に少なくなるように設定できることです。開発者は普段通りにコードを記述するだけで、変更にサブスクライブ(購読)することで、コンテンツを自動的に新鮮な状態に保つことができます。

これは、以下の方法で実現されます。

  • データがフェッチされる場所に use cache を局所化する。
  • データが変更された際に revalidateTag を呼び出す。
  • 変更されたデータのみを再検証するために、特定のタグ(narrow tags)を使用する。

まとめ

Next.js App Routerにおけるuse cacheは、リアクティブな状態管理と効率的なキャッシングを実現するための強力なプリミティブです。フレームワークがデータの依存関係を深く理解することで、開発者は手動での複雑な無効化作業から解放され、より高速で、常に最新のデータを表示する高品質なアプリケーションを構築できます。

use cacheを活用することで、開発者はキャッシングロジックに煩わされることなく、ビジネスロジックに集中できるため、開発者体験が大幅に向上し、ユーザーにとってもより良い体験を提供できるようになるでしょう。


参考動画

https://www.youtube.com/watch?v=d-hvEqWb79c