Next.jsの4つのキャッシュメカニズムについて
概要
Next.jsのキャッシュについてドキュメントの確認
Next.jsはレンダリングワークとデータリクエストをキャッシュすることで、アプリケーションのパフォーマンスを改善し、コストを削減します。
このページでは、Next.jsのキャッシュメカニズム、それらを構成するために使用できるAPI、およびこれらがどのように相互作用するかについて詳しく説明します。
4つのキャッシュメカニズム
Mechanism | What | Where | Purpose | Duration |
---|---|---|---|---|
Request Memoization | Return values of functions | Server | Re-use data in a React Component tree | Per-request lifecycle |
Data Cache | Data | Server | Store data across user requests and deployments | Persistent (can be revalidated) |
Full Route Cache | HTML and RSC payload | Server | Reduce rendering cost and improve performance | Persistent (can be revalidated) |
Router Cache | RSC Payload | Client | Reduce server requests on navigation | User session or time-based |
デフォルトでは、Next.jsはパフォーマンスを向上させ、コストを削減するために可能な限りキャッシュします。これは、opt out
(キャッシュ機能を除外、無効化)しない限り、ルートが静的にレンダリングされ、データリクエストがキャッシュされるということを意味します。下の図は、ルートがビルド時に静的にレンダリングされ、静的なルートが初めて訪れた場合のデフォルトのキャッシング動作を示しています。
キャッシュの動作は、ルートが静的にレンダリングされるか動的にレンダリングされるか、データがキャッシュされているかキャッシュされていないか、リクエストが初回の訪問か後続のナビゲーションかによって変わります。ユースケースに応じて、個々のルートやデータリクエストのキャッシング動作を構成できます。
Request Memoization
Reactはfetch
APIを拡張して同じURLとオプションをもつリクエストを自動的にMemoization(メモ化)します。
これは、Reactコンポーネントツリー内の複数の場所で同じデータのfetch関数を呼び出すことができ、関数はただ一度だけ実行されるということを意味します。
例えば、同じデータをルート全体で使用する必要がある場合(例:Layout、Page、および複数のコンポーネントで)、データをツリーの上でフェッチしてからコンポーネント間でprops
を渡す必要はありません。props
を渡す代わりに、同じデータに対してネットワークを介して複数のリクエストを行う際のパフォーマンスの影響を心配せずに、それを必要とするコンポーネントでデータをfetchできます。
async function getItem() {
// The `fetch` function is automatically memoized and the result
// is cached
const res = await fetch('https://.../item/1')
return res.json()
}
// This function is called twice, but only executed the first time
const item = await getItem() // cache MISS
// The second call could be anywhere in your route
const item = await getItem() // cache HIT
これにより、複数コンポーネントで同一のデータを利用する場合も、上位コンポーネントからpropsでデータを渡す必要はありません。
代わりに、同じデータに対してネットワーク上で複数のリクエストを行うことによるパフォーマンスへの影響を心配することなく、必要なコンポーネントでデータをフェッチできます。
Request Memoizationの動作
- ルートのレンダリング中に、初回リクエストが呼ばれた時は、そのリクエスト結果はメモリに存在しないため、キャッシュ
MISS
となる。 - キャッシュ
MISS
となったので、その関数は実行される。そしてデータが外部ソースからfetchされて、結果はメモリに保存される。 - 同じレンダーパス内での後続のリクエスト関数の呼び出しはキャッシュ
HIT
となる。なので関数は実行されることなく、メモリからデータが返される。 - ルートがレンダリングされて、レンダリングパスが完了すると、メモリはリセットされ、リクエストの
memoization entries
(メモ化エントリ)はクリアされる。
Good to know:
- Request memoizationはReact機能で, Next.jsの機能ではありません. 他のキャッシュメカニズムとどのように相互作用をするかを示すためにここに含まれています。
- Memoizationはfetchリクエストの
GET method
にのみ適用されます。 - Memoizationは
React Component tree
にのみ適用されます。つまり:-
generateMetadata
,generateStaticParams
,Layouts
,Pages
と他のServer Components
のfetch requestsに適用されます。 -
Route Handlers
のfetch requestsはReact component tree
の一部ではないため適用されません。
-
- fetchが適してない場合(例えば
database clients
、CMS clients
、GraphQL clients
)、メモ化するためにReactcache
functionを使うことができます。
Duration(存続期間)
キャッシュは、Reactコンポーネントツリーのレンダリングが完了するまで、サーバーリクエストの間、存続します。
Revalidating(再検証)
memoization(メモ化)はサーバーリクエスト間で共有されず、レンダリング間だけ適用されるため、revalidate(再検証)する必要はありません。
Opting out(除外、無効化)
fetch
リクエストのmemoization(メモ化)を無効にするには, AbortController signal
をリクエストに渡します。
const { signal } = new AbortController()
fetch(url, { signal })
Data Cache
Next.jsにはサーバーリクエストやデプロイメントをまたいでのデータfetchの永続化を行う、組み込みのData Cache
があります。これが可能なのは、Next.jsがサーバー上の各リクエストを独自の永続的なキャッシュセマンティクスにセットできるようにしたためです。
Good to know:
ブラウザでは、fetchのcacheオプションはリクエストがブラウザのHTTPキャッシュとどのように相互作用するかを示しますが、Next.jsでは、cacheオプションはサーバーサイドのリクエストがサーバーのデータキャッシュとどのように相互作用するかを示します。
デフォルトでは、fetch
を使うデータのリクエストはキャッシュされます。
キャッシュの動作を構成するには、fetch
のcacheやnext.revalidateオプションを使うことができます。
Data Cacheの動作
- レンダリング中に初回の
fetch
リクエストが呼ばれる、Next.jsはData Cache
にキャッシュされたレスポンスがあるかチェックする。 - キャッシュされたレスポンスが見つかった場合、レスポンスはすぐにreturnされmemoized(メモ化)される。
- キャッシュされたレスポンスが見つからなかった場合, リクエストがデータソースに対して行われ、結果が
Data Cache
に保存され、memoized(メモ化)される。 - キャッシュしないデータでは (例:
{ cache: 'no-store' }
)、結果は常にデータソースからfetchされて、memoized(メモ化)される。 - データがキャッシュされるかどうかにかかわらず、リクエストは常にmemoized(メモ化)され、Reactのレンダーパスの間に同じデータに重複したリクエストを行うことを防ぎます。
Data CacheとRequest Memoizationの違い
両方のキャッシュメカニズムはキャッシュデータを再利用することでパフォーマンスの向上に役立ちますが、Data Cache
はリクエストやデプロイメントをまたいで永続的です。一方memoization
(メモ化)の期間はリクエストの生存期間のみです。
memoization
(メモ化)では、レンダリングサーバーからデータキャッシュサーバー(CDN やエッジ ネットワークなど) またはデータソース(データベースやCMSなど)までネットワーク境界を越える必要がある、同じレンダーパスの中で重複したリクエストの数を減少させることができます。
Data Cache
では、オリジンデータソースへのリクエストの数を減少させることができます。
Duration(存続期間)
Data Cache
はrevalidate
(再検証)やopt-out
(除外、無効化)をしない限り、リクエストやデプロイメントをまたいで永続化されます。
Revalidating(再検証)
キャッシュされたデータは次の2つの方法でrevalidated
(再検証)することができます:
-
Time-based Revalidation(時間ベースの再検証): 一定期間が過ぎて、新しいリクエストが行われた時、データを
revalidate
(再検証)します。 頻繁に変更されない、新鮮さが重要でないデータに対して有用です。 -
On-demand Revalidation(要求ベースの再検証): イベント (例: フォームの送信)に基づいてデータを
revalidate
(再検証)します。On-demand revalidation
(要求ベースの再検証)は一度に複数のグループのデータをrevalidate
(再検証)するためにタグベース
やパスベース
のアプローチができます。できるだけ早く最新のデータが表示されるようにしたい場合に有用です(例: ヘッドレスCMSからのコンテンツが更新された時)。
Time-based Revalidation(時間ベースの再検証)
一定の時間間隔でデータをrevalidate
(再検証)するには、fetch
のnext.revalidate
オプションを使うことができ、秒単位でキャッシュの存続期間をセットできます。
// Revalidate at most every hour
fetch('https://...', { next: { revalidate: 3600 } })
または、セグメント内のすべてのfetch
リクエストの設定や、fetch
が利用できない場合のために、Route Segment Config optionsを使うことができます。
Time-based Revalidationの動作
-
revalidate
(再検証)と共に初回のfetch
リクエストが呼ばれたら、データは外部データソースからfetchされ、Data Cache
に保存されます。 - 指定された時間枠内(例: 60秒)に行われたリクエストはキャッシュされたデータを返します。
- 時間枠が経過した後も、次のリクエストはまだキャッシュされた(古い)データを返します。
- Next.jsはバックグラウンドでデータの
revalidation
(再検証)をトリガーします。 - データのfetchに成功したら、Next.jsは
Data Cache
を新しいデータで更新します。 - もしバックグラウンドで
revalidation
(再検証)が失敗したら、以前のデータが置き換わらずに保持されます。
- Next.jsはバックグラウンドでデータの
これはstale-while-revalidateのふるまいに似ています。
On-demand Revalidation(要求ベースの再検証)
データは、パス(revalidatePath)、またはキャッシュタグ(revalidateTag)によってオンデマンドでrevalidate
(再検証)が可能です。
On-Demand Revalidationの動作
- 初回のfetchリクエストが行われたら、データは外部データソースからfetchされ、
Data Cache
に保存されます。 -
on-demand revalidation
がトリガーされた時、適切なキャッシュエントリがキャッシュから削除されます。- これは
time-based revalidation
と違います。time-based revalidation
は新しいデータが取得されるまでキャッシュ内に古いデータが保持されます。
- これは
- 次のリクエストが行われた時、再びキャッシュ
MISS
となり、データは外部データソースからfetchされ、Data Cache
に保存されます。
Opting out(除外、無効化)
個々のデータのfetchでは、cacheオプションをno-store
に設定することで、キャッシュを無効ににできます。これは、fetch
が呼び出されるたびにデータが取得されることを意味します。
// Opt out of caching for an individual `fetch` request
fetch(`https://...`, { cache: 'no-store' })
または、Route Segment Config optionsを使うことで、特定のルートセグメントに対するキャッシュを無効にできます。これはサードパーティライブラリも含めて、ルートセグメント内の全てのデータリクエストに影響します。
// Opt out of caching for all data requests in the route segment
export const dynamic = 'force-dynamic'
Vercel Data Cache
もしNext.jsのアプリケーションがVercelにデプロイされているのであれば、Vercelの特徴を理解するために、Vercel Data Cache のドキュメントを読むことをお勧めします。
Full Route Cache
Next.jsはビルド時に自動的にルートのレンダリングとキャッシュをします。
これは最適化で、リクエストごとにサーバーでレンダリングする代わりにキャッシュされたルートを提供することで、ページの読み込みを高速化します。
Full Route Cache
を理解するためには、Reactがどのようにレンダリングを行うか、Next.jsがどのように結果をキャッシュするかを見ることが役立ちます。
1. サーバー上のReactレンダリング
サーバー上では、Next.jsはレンダリングを調整するためにReactのAPIを使用します。レンダリング作業は、個々のルートセグメントとSuspenseの境界ごとにチャンクに分割されます。
それぞれのチャンクは2つのステップでレンダリングされます。
- Reactは
Server Components
をストリーミングに最適化された特別なデータフォーマットにレンダリングします。これはReact Server Component Payload
と呼ばれます。 - Next.jsは
React Server Component Payload
とClient Component JavaScript
の命令を使って、サーバー上でHTML
をレンダリングします。
これは、作業をキャッシュしたりレスポンスを送信したりする前に、すべてがレンダリングされるまで待つ必要がないことを意味します。 代わりに、作業が完了するにつれてレスポンスをストリームできます。
React Server Component Payloadとは
React Server Component Payload
はレンダリングされた React
サーバーコンポーネントツリーのコンパクトなバイナリ表現です。それはブラウザのDOMを更新するために、Reactによってクライアント上で使用されます。
React Server Component Payload
は下記を含みます:
-
Server Components
のレンダリング結果 -
Client Components
がレンダリングされるべき場所や、JavaScriptファイルへの参照のためのプレースホルダ -
Server Component
からClient Component
へ渡されるprops
2. サーバー上のNext.jsのキャッシュ(Full Route Cache)
Next.jsのデフォルト動作はルートのレンダリング結果(React Server Component Payload
とHTML
)をサーバー上でキャッシュすることです。これはビルド時の静的レンダリングされたルートや、revalidation
(再検証)の間に適用されます。
3. クライアント上のReact HydrationとReconciliation
リクエスト時、クライアント側では:
-
HTML
は、Client
とServer Components
の非対話型の高速な初期プレビューを即座に表示するために使用されます。 -
React Server Components Payload
は、Client
とレンダリングされたServer Components
のツリーを調整し、DOMを更新するために使用されます。 - JavaScriptの命令は、
Client Components
をhydrateし、アプリケーションを対話的にするために使用されます。
4. クライアント上のNext.jsのキャッシュ(Router Cache)
React Server Component Payload
はクライアントサイドのRouter Cacheに保存されます。これは個々のルートセグメントで分割された、個別のインメモリキャッシュです。
このRouter Cache
は、過去に訪れたルートを保存し、将来のルートをprefetchingすることで、ナビゲーションのエクスペリエンスを向上させるために使用されます。
5. 後続のナビゲーション
後続のナビゲーションやprefetching中に、Next.jsはReact Server Components Payload
が
Router Cache
に保存されているかを調べます。もし存在すれば、新しいリクエストをサーバーに送信せずにスキップします。
もしルートセグメントがキャッシュになければ、Next.jsはサーバーからReact Server Components Payload
を取得し、クライアント上のRouter Cache
にデータを格納します。
静的、動的レンダリング
ビルド時にルートがキャッシュされるかどうかは、そのルートが静的
にまたは動的
にレンダリングされるかどうかによります。静的ルートはデフォルトでキャッシュされます。動的ルートはリクエスト時にレンダリングされ、キャッシュされません。
下の図は、静的にレンダリングされるルートと動的にレンダリングされるルート、キャッシュされたデータとキャッシュされていないデータの違いを示しています。
詳細はstatic and dynamic rendering
Duration(存続期間)
デフォルトでは、Full Route Cache
は永続的です。これは、レンダリングのアウトプットがユーザーのリクエストをまたいでキャッシュされることを意味します。
Invalidation(無効化)
Full Route Cache
をinvalidate
(無効化)する二つの方法があります。:
-
Revalidating Data(データの再検証): Data Cacheを
revalidate
(再検証)することで、サーバー上でコンポーネントを再レンダリングし、新しいレンダリングのアウトプットをキャッシュすることにより、Router Cache
が無効になります。 - Redeploying(再デプロイ):
Data Cache
がデプロイをまたいで永続するのとは異なり、Full Route Cache
は新しいデプロイ時にクリアされます。
Opting out(除外、無効化)
Full Route Cache
を無効にできます。言い換えれば、すべてのリクエストに対して動的にコンポーネントをレンダリングする方法は、以下のようになります:
-
Dynamic Functionを使用: これによりルートは
Full Route Cache
からOpting out(除外、無効化)され、リクエスト時に動的にレンダリングされます。Data Cache
は引き続き使用できます。 - route segment config optionsの
dynamic = 'force-dynamic'
またはrevalidate = 0
を使用: これはFull Route Cache
とData Cache
をスキップします。つまり、コンポーネントはレンダリングされ、サーバーに対するすべてのリクエストでデータはfetchされます。Router Cache
はクライアントサイドのキャッシュとして引き続き適用されます。 -
Data CacheのOpting out(除外、無効化): もしルートにキャッシュされてないfetchリクエストがある場合、そのルートは
Full Route Cache
から除外されます。特定のfetchリクエストのデータは、すべてのリクエストごとに取得されます。キャッシュをOpting out(除外、無効化)しない他のfetchリクエストは引き続きData Cache
にキャッシュされます。これにより、キャッシュされたデータとキャッシュされていないデータのハイブリッドが可能となります。
Router Cache
Next.jsはユーザセッションの間、React Server Component Payload
を個々のルートセグメントごとに分割して保存するインメモリのクライアントサイドのキャッシュを持ちます。
Router Cacheの動作
ユーザーがルート間をナビゲートする時、Next.jsは訪れたルートセグメントをキャッシュし、ユーザーがナビゲートする可能性のあるルートを(ビューポート内の<Link>コンポーネントに基づいて)prefetchingします。
これにより、ユーザーは改善されたナビゲーション体験が得られます:
- 訪れたルートがキャッシュされているため、即座の戻る/進むナビゲーションが可能であり、prefetchingとpartial renderingにより新しいルートへの高速なナビゲーションが行えます。
- ナビゲーション間にはフルページの再読み込みがなく、Reactの状態とブラウザの状態が保持されます。
Router CacheとFull Route Cacheの違い:
Router Cache
はReact Server Component Payload
をユーザーセッションの間に一時的にブラウザーに保存します。
一方、Full Route Cache
はReact Server Component Payload
とHTML
を複数のユーザーリクエストをまたいで、永続的に保存します。
Full Route Cache
は静的にレンダリングされたルートのみをキャッシュしますが、Router Cache
は静的にも動的にもレンダリングされたルートの両方に適用されます。
Duration(存続期間)
キャッシュはブラウザのメモリに保存されます。2つの要素がキャッシュの存続期間を決定します:
- Session: キャッシュはナビゲーションをまたいで存続します。しかし、ページをリフレッシュするとクリアされます。
- Automatic Invalidation Period: 個々のセグメントのキャッシュは特定の時間が経過すると自動的に無効になります。期間はルートが静的にレンダリングされたか、動的にレンダリングされたかによって決まります。:
- 動的レンダリング: 30 秒
- 静的レンダリング: 5 分
ページのリフレッシュはすべてのキャッシュされたセグメントをクリアしますが、自動的な無効化期間は、最後にアクセスまたは作成された時点から個々のセグメントにのみ影響します。
動的にレンダリングされたルートに対してprefetch={true}
を追加するか、またはrouter.prefetch
を呼び出すことで、そのルートを5分間キャッシュするようにできます。
Invalidation(無効化)
Router Cache
を無効にする方法は2つあります。:
-
Server Action
の中で:- パス(revalidatePath)による、またはキャッシュタグ (revalidateTag)によるデータの再検証の要求
-
cookies.setまたはcookies.deleteを使用すると、クッキーを使用するルートが
stale
となることを防ぐために、Router Cache
が無効になります(例:認証).
- Calling router.refreshを呼び出すと、
Router Cache
が無効化され、現在のルートに対して新しいリクエストがサーバーに送信されます。
Opting out(除外、無効化)
Router Cache
をOpting out(除外、無効化)することはできません。しかし、
router.refresh、revalidatePath、revalidateTagを呼ぶことでinvalidate
(無効化)できます。 これによりキャッシュがクリアされ、サーバーへの新しいリクエストが行われて、最新のデータが表示されます。
<Link>
コンポーネントのprefetch
プロパティをfalse
に設定することで、prefetching
をopt out
することもできます。ただし、タブバーや戻るや進むナビゲーションなど、ネストされたセグメント間の迅速なナビゲーションを可能にするために、ルートセグメントが一時的に30秒間保持されます。訪れたルートは引き続きキャッシュされます。
キャッシュの相互作用
異なるキャッシングメカニズムを構成する時には、それらがどのように互いに影響するかを理解することが重要です。
Data Cache と Full Route Cache
- レンダリングのアウトプットはデータに依存するため、
Data Cache
のRevalidating
(再検証)やopting out
(除外、無効化)はFull Route Cache
をinvalidate
(無効化)します。 -
Full Route Cache
のInvalidating
(無効化)やopting out
(除外、無効化)はData Cache
に影響しません。キャッシュされたデータとキャッシュされていないデータの両方を持つルートを動的にレンダリングできます。これは、ページの大部分がキャッシュデータを使用しているが、リクエスト時にフェッチする必要があるデータを依存するいくつかのコンポーネントがある場合に役立ちます。すべてのデータを再度フェッチするパフォーマンスの影響を気にせずに動的にレンダリングできます。
Data Cache と Client-side Router cache
-
Route Handlerで
Data Cache
のrevalidating
(再検証)をしても、Route Handler
は特定のルートに結び付いていないため、Router Cache
はすぐにはinvalidate
(無効化)になりません。 これはハードリフレッシュが行われるか、automatic invalidation period
(自動的な無効化期間)が経過するまで、Router Cache
が以前のペイロードを提供し続けることを意味します。 -
Data Cache
とRouter Cache
を即時にinvalidate
(無効化)にするためには、Server Action内でrevalidatePathまたはrevalidateTagを使用します。
Discussion