この記事を書こうと思った理由
今回はサイバーエージェント様の『【学生歓迎】ユーザー数〇〇万人の大規模サービスを支えるデータストアとキャッシュ戦略』という登壇を受けてフロントエンドでもキャッシュについて勉強してみました。今回はNext.jsのキャッシュについてドキュメントを参考に書いていこうと思います。
そもそもキャッシュとは何か?
キャッシュとは、よく利用されるデータを一時的に保存して、再利用を容易にする仕組みです。
図書館を例にとると、頻繁に借りられる本をフロント近くに置いておくことで、利用者はすぐにその本を手に取ることができます。
同じように、頻繁にリクエストされるデータを保存しておくことで、通信回数を減らし、アプリケーションの速度を向上させることができます。
リクエストのメモ化 (Request Memoization)
1. 重複排除
同じリクエストを繰り返す場合、そのリクエストをキャッシュして、重複を排除します。
たとえば、同じAPIエンドポイントに対して同じパラメータでリクエストを送信する場合、キャッシュを利用して再度サーバーに問い合わせることを避けることができます。
注意点
- URLやオプションがわずかに異なる場合、メモ化されずに新しいリクエストとして扱われます。
-
fetch()
を使う際は、リクエストを関数化しておくとキャッシュの重複排除が行いやすくなります。ファイルやデータフェッチ層でエンドポイントを固定することで、メモ化の機能が確保されます。
2. 使用可能なケース
-
serverComponent
でfetch()
を使用する場合に適用されます。 - ただし、ORMで作成した関数や
RouteHandler
(API)内で使用する場合はメモ化されません。 -
fetch()
以外でのキャッシュにはReactのcache
を使用します。
3. 最下層コンポーネント
- コンポーネントの最下層で
fetch()
を行っても問題ありません。
4. 動的メタデータの設定
-
generateMetaData()
のような関数を使う場合、同じリクエストがメモ化されて重複排除が行われます。
5. キャッシュ期間
- キャッシュは永続的ではなく、
fetch()
のリクエストごとに保存されます。
データキャッシュ (Data Cache)
- fetch('', { cache: 'force-cache' })
キャッシュに強制的にアクセスするリクエストです。
MISS: 初回リクエスト時はキャッシュが存在しないためキャッシュミスが発生し、データソースからデータを取得します。
SET: データを取得した後、Request MemoizationとData Cacheの両方にキャッシュが設定されます。
HIT: 次回以降のリクエスト時にはキャッシュが存在するため、キャッシュからデータが取得され、データソースへアクセスすることなく処理が完了します。 - fetch('', { cache: 'no-store' })
このオプションではキャッシュを使用せず、常に最新のデータを取得します。
MISS: キャッシュを参照せず直接データソースにアクセスするため、Request MemoizationでもData Cacheでもミスが発生します。
SKIP: キャッシュへの保存をスキップし、データソースからデータを取得します。
HIT: 直接データソースからデータが取得されるため、常に最新のデータが提供されます。
1. 全体的なデータキャッシュ
データ全体に影響するキャッシュを指します。
適切に理解して使用しないと、予期しないデータが返される可能性があります。
2. fetch()
でキャッシュ設定
-
cache: "force-cache"
:強制的にキャッシュを使用 -
cache: "no-store"
:キャッシュを使用せず最新データを取得 -
cache: {next: {revalidate: 3600}}
:再検証を行いキャッシュを更新
SSR/SSGとキャッシュ
- サーバーサイドレンダリング(SSR)やスタティックサイトジェネレーション(SSG)に関連します。
3. キャッシュ期間
- 永続的で、サーバーの再起動やビルドの再実行でもキャッシュは残ります。
- 最新データを返すためにはSSRにしたり、
no-store
オプションを指定する必要があります。現在のバージョンでは、デフォルトでno-store
です。
ルート全体のキャッシュ (Full Route Cache)
1. ページ全体のキャッシュ
ページ全体のHTMLやRSC(React Server Component)ペイロードをキャッシュします。
個別のfetch()
リクエストのキャッシュとは異なり、ページのコンテンツ全体を対象としています。
2. スタティックレンダリングのみ適用
- SSG/ISR(Incremental Static Regeneration)のときのみ適用されます。
- SSRの場合はキャッシュされません。
3. キャッシュ期間
- 永続的で、ユーザー間でキャッシュが共有されます。ただし、臨時的なキャッシュには慎重に対応が必要です。
ルーターキャッシュ (Router Cache)
1. ナビゲーション用のキャッシュ
ページを訪問すると、そのRSCペイロードが自動的にキャッシュされます。
そのため、"戻る"や"進む"といったナビゲーションが高速になり、ユーザーエクスペリエンスが向上します。
2. <Link>
タグ
-
静的ページ:
prefetch
がデフォルトでtrue
です。事前にページがプリロードされているため、リンクがクリックされた瞬間にページ遷移が可能です(本番環境のみ有効)。 -
動的ページ:共通のレイアウト部分は
prefetch
されますが、動的な部分はloading.js
でローディングUIを表示する必要があります。
3. キャッシュを無効化
-
revalidatePath
やrevalidateTag
をServerActions
で使用
revalidatePath
はデータを手動で再検証し、特定のパスの下のルート セグメントを 1 回の操作で再レンダリングできます。
revalidateTag
はタグに関連付けられたキャッシュのエントリを消去できます。 -
cookies.set
/cookies.delete
Server Actionでcookies.set
やcookies.delete
を使用すると、ルーターキャッシュが無効化されます。これは、クッキーを使用するルートが最新でない状態(例えば、認証情報の変更を反映する必要がある場合)になるのを防ぐためです -
router.refresh
Next.js の App Router の機能で、ページ全体を再レンダリングして最新のデータを取得する際に使用されます。このメソッドは、特定の状況でキャッシュが無効化されたり、データが更新された際に、ユーザーに最新の状態を反映させたい場合に役立ちます
4. キャッシュ期間
- セッション中のみ有効(ブラウザを閉じるまで)
- 静的ページ:デフォルトで5分
- 動的ページ:デフォルトで30秒
-
staleTimes
で詳細な時間指定が可能
最後に
1. キャッシュの挙動を理解する
- 予期しないキャッシュが発生しないようにする
- キャッシュすべき箇所を把握する
- ドキュメントで最新情報を確認する
2. キャッシュをより細かく制御したい場合
- Remixなどのフレームワークを利用
- Web APIの標準仕様に従う
- キャッシュの詳細なカスタマイズが可能
キャッシュはアプリケーションによってどこで行うのかが異なるため、そのアプリのドメインを理解してどのキャッシュ機能を使うべきかを考えられるといいですね。next.jsだけではくて色々なものに触れベストプラクティスを模索できるようになろう!!