Next.jsが標準で提供するもの

  • データキャッシュ – Next.js 13以降では、すべてのfetch()呼び出しがリクエストごとのメモリキャッシュに格納されます。revalidateオプションを追加すると、ランタイムに一定間隔でデータを更新するように指示でき、頻繁なAPI呼び出し(hot call)を、必要な時だけ実行される呼び出し(cold call)に変えることができます。
  • フルルートキャッシュ – フレームワークは、レンダリングされたHTMLとルート全体に必要なデータを保持します。再訪問者は、サーバーが再レンダリングをスキップするため、瞬時に画面遷移できます。
  • ISR – 古いバージョンがトラフィックを処理し続けている間に、バックグラウンドで静的ページが再生成されます。これにより、コンテンツが変更されるたびにフルビルドすることなく、大規模なサイトを静的な状態に保つことができます。
  • Server Componentのcache() – Reactのcacheヘルパーは、単一のリクエストの間、高コストな計算やデータベース接続をメモ化し、ページ内のコンポーネントツリー内での重複した作業を回避します。

これらのキャッシュは「初回アクセス時」の問題を解決しますが、依然としてNodeプロセス内に存在します。サーバーとデータベースを保護するには、キャッシュをより外側へと押し出しましょう。

追加可能な外部レイヤー

レイヤー 保存内容 代表的なツール メリット
CDN 静的アセット、HTML、API JSON Cloudflare, Akamai, AWS CloudFront コンテンツをエッジロケーションに移動し、ユーザーへのラウンドトリップを短縮する
リバースプロキシ Nodeに到達する前のページレスポンス全体 Nginx, Varnish キャッシュされたページを直接配信し、Next.jsインスタンスへの負荷を軽減する
アプリケーションレベルのキャッシュ 高コストなDBクエリやAPI呼び出しの結果 Redis, Memcached サーバーの再起動後も存続し、複数のアプリインスタンス間で共有可能な高速なキーバリューストアを提供する

各レイヤーはオリジンデータベースから遠い位置に配置されるため、あるレベルでのキャッシュミスは次のレベルへと連鎖し、最終的にどうしても必要な場合にのみデータベースに到達します。

キャッシュの鮮度を保つ

キャッシュの無効化(Invalidation)は、多くのチームが躓くポイントです。実用的な3つのパターンを紹介します。

  • 時間ベース (TTL) – キャッシュエントリに固定の有効期限を割り当てます。設定は簡単ですが、タイマーが切れるまで古いデータが提供される可能性があります。
  • イベント駆動 – CMSや、コンテンツ変更時にWebhookを発行するデータソースに連携させます。Webhookがトリガーとなり、関連するキャッシュエントリのパージ(削除)を実行します。
  • タグベース – 一連のfetchに論理的なタグ(例:product-list)を付与します。そのグループ内のいずれかのアイテムが変更された際、revalidateTag('product-list')を一度呼び出すだけで、そのタグを共有するすべてのエントリをクリアできます。

これらのアプローチを組み合わせることで、データの鮮度とキャッシュヒット率のバランスを取ることができます。

多くのチームに適した階層構造

  1. ブラウザキャッシュ – 静的アセット(CSS、JS、画像)に長いmax-ageを設定し、ユーザーのデバイスがネットワークに再度問い合わせる必要がないようにします。
  2. CDN – エッジノードが完全なHTMLページとAPI JSONをキャッシュし、Next.jsで設定したCache-Controlヘッダーに従います。
  3. リバースプロキシ – NginxやVarnishインスタンスがNext.jsサーバーの前面に配置され、めったに変更されないルートに対してキャッシュされたレスポンスを配信します。
  4. Next.js内部キャッシュ – フレームワークのデータキャッシュとルートキャッシュが、リクエストごとのメモ化やISRを処理します。
  5. アプリケーションキャッシュ – Redisが、クエリパラメータやタグをキーとして、重いデータベースクエリの結果を保存します。
  6. データベース – 究極の信頼できる情報源(Source of Truth)であり、上位のすべてのレベルでキャッシュミスが発生した場合にのみクエリされます。

リクエストが届くと、いずれかのレイヤーが応答するまでこのリストを順に辿ります。最も速い応答が採用され、そのレスポンスは将来のヒットに備えてチェーンを遡って書き込まれます。

各要素を組み合わせる

  1. APIルートにCache-Controlヘッダーを設定する
  2. CDNを構成する – HTMLとJSONエンドポイントのエッジキャッシュを有効にし、CDNがCache-Controlを尊重するように設定します。
  3. リバースプロキシをデプロイする – Next.jsサーバーの前にNginxを配置します。
  4. Redisを追加する – 高コストなデータベースクエリの結果をキャッシュします。
  5. コンポーネントでrevalidateTagを使用するrevalidateTagを呼び出して、特定のタグに対するNext.js内部キャッシュをクリアします。

まとめ

Next.js内の単一のキャッシュは初回リクエストを高速化しますが、真のスケーラビリティは、規律あるスタック(ブラウザ → CDN → リバースプロキシ → フレームワーク → Redis → データベース)から生まれます。各レイヤーを構成し、時間、イベント、タグを用いて無効化を管理し、メトリクスを監視しましょう。これにより、バックエンドがトラフィックの急増に耐えつつ、レイテンシを低く保つことができます。