• トップ
  • ブログ一覧
  • 【Next.js】App Router で使用できるキャッシュまとめ
  • 【Next.js】App Router で使用できるキャッシュまとめ

    モーリー(エンジニア)モーリー(エンジニア)
    2024.03.29

    IT技術

    今回は Next.js の App Router で使えるキャッシュの理解をざーっと深めていきたいと思います!

    というのも App Router で Pages Router より細かなキャッシュ戦略を使用することができそうだとは思っていたのですが、細かくどんなことができるのかまでは確認していませんでした。

    最近リリースされた Next.js v14.1 では、今まで各 Next.js サーバーのファイルシステム(ディスク)にしか保存することができなかったキャッシュの保存先を、Redis や AWS S3 などに設定することができるようになりました。そのため、クラウドサービスを使用したサーバーインスタンス数をスケールアウトさせるケースなどにも各インスタンス間でキャッシュを共有することが可能になりました。

    このようにNext.jsでのキャッシュ周りの利便性が高くなってきていますので、今一度 Next.js の App Router で使用できるキャッシュをまとめてみようと思います!

    App Router で使用できるキャッシュの種類は4種類

    App Router で使用できるキャッシュの種類は下記4つになります。( Next.js 公式サイトから抜粋 )

    MechanismWhatWherepurposeDuration
    Request MemoizationReturn values of functionsServerRe-use data in a React Component treePer-request lifecycle
    Data CacheDataServerStore data across user requests and deploymentsPersist (can be revalidated)
    Full Route CacheHTML and RSC payloadServerReduce rendering cost and improve performancePersist (can be revalidated)
    Router CacheRSC PayloadClientReduce server requests on navigationUser session or time-based

    それぞれ、クライアントでのキャッシュかサーバー側でのキャッシュかで分かれています。

    では一つずつ確認していきましょう。

    Request Memoization

    これは 1 サーバーリクエストごとのキャッシュになります。

    1 サーバーリクエスト間で複数回、React Component Tree の様々な場所で同じ URL に同じオプションで fetch リクエストを行っていても、React が fetch の結果をキャッシュしてくれるためキャッシュ生成後のリクエストは全て実際にリクエストを飛ばさずキャッシュの値を自動的に使用してくれます。そのため、パフォーマンスのことを考えて Top のコンポーネントからリクエストを飛ばし、そのデータを実際に使用する子コンポーネントに橋渡ししていくなどといった必要がなくなります。

    そして 1 サーバーリクエスト間でのキャッシュであるため、レンダリングが完了すると全ての Request Memoization はクリアされます。

    *注意
    Request Memoization は fetch を使用した GET リクエストにしか適用されません。
    (Graphql クライアントなどを使用していて fetch を利用していない場合、react の cache function を使用することでデータのキャッシュが行えます。)

    Request Memoization の流れ

    ( Next.js 公式サイトから抜粋 )

    キャッシュの無効化

    AbortControllersignal を使用することで、キャッシュを無効化できます。

    1const { signal } = new AbortController()
    2fetch(url, { signal })

    Data Cache

    Data Cache は fetch を利用して取得したデータを、Next.js サーバーへのリクエスト、デプロイメント間に渡ってキャッシュ化し永続化します。デプロイメント間に渡って永続化されるというのは、ビルドを行なって新しいバージョンを作成したとしても、キャッシュのデータは引き継がれるということです。

    Data Cache の流れ

    ( Next.js 公式サイトから抜粋 )

    キャッシュの再検証

    Data Cache は2つの方法で再検証することができます。

    • 時間方式:
      時間方式は、fetch時に設定した時間を経過後に新しくリクエストが来た場合キャッシュを再作成する方式です。
      *注意ポイント
      設定時間経過後のリクエストで返却される値は以前のキャッシュになります。設定時間経過後のリクエストを返却後、Next.js が裏側で新たにデータソースにリクエストをなげキャッシュを更新します。
      1fetch('https://...', { next: { revalidate: 3600 } })
    • オンデマンド方式:
      この方式は、revalidatePath または revalidateTag を使用して、すぐに Next.js にキャッシュを更新させる方式です。
      1// '/items' route内のコンポーネント
      2// タグ付きでデータをキャッシュ
      3fetch(`https://...`, { next: { tags: ['a', 'b', 'c'] } })
      4
      5------------------------------
      6
      7// パスベースで再検証
      8revalidatePath('/items')
      9
      10// タグベースで再検証
      11revalidateTag('a')

    キャッシュの無効化

    Data Cache の無効化方法には下記の二つがあります。

    • 個別に無効化する
      fetch の cache オプションに no-store を設定することで、各 fetch リクストごとにデータソースにデータを取得することができます。
      1fetch(`https://...`, { cache: 'no-store' })
    • ルート全体を無効化する
      Route Segment Config を使用して、特定のルート全体の Data Cache を無効化できます。これはサードパーティーのライブラリにも適用されます。
      1// layout.tsx | page.tsx | route.ts の中で
      2export const dynamic = 'force-dynamic'

    Full Route Cache

    Full Route Cache は(基本的に)ビルド時に生成されるキャッシュです。

    Next.js はビルド時に静的なルートのHTMLとServer Component Payloadを生成します。

    ユーザーからのリクエスト時に、Full Route Cache でキャッシュされているHTMLを直ちに返すことで素早くユーザーに画面を表示することができます。その後、クライアント側でServer Component Payloadを使用してClient ・Server Component tree のすり合わせをしDOMを更新します。

    Full Route Cache の流れ

    ( Next.js 公式サイトから抜粋 )

    💡 Server Component Payload とは?
    下記の情報を含んだクライアント側で DOM の更新に利用されるデータのこと
    ・Server Component のレンダリング結果
    ・クライアント・コンポーネントをレンダリングする場所を示す値と、その JavaScript ファイルへの参照
    ・Server Component から Client Component へ渡される props

    (詳しくは次のブログ記事で書こうと思います。)

    ここでいう静的なルートとは、ざっくりいうと Dynamic Function を使用していないルートのことを言います。

    Dynamic Function とは、cookie や request headers 、URL search parameters などを取得するファンクションのことです。これらの function を使用することで各ユーザーごとに必要なデータを使用してユーザー各々に最適化されたページを生成できます。

    ただ、大抵のウェブサイトではページ内のすべての箇所でユーザー固有のデータを持つのではなく、一部はキャッシュしたデータを使用し、他の一部ではCookieなどを使用してユーザー固有のデータを表示させると言ったどちらも使用したケースがほとんどです。Next.js では React Server Component Payload と Data Cache は別々に保存されるため、複合系のケースでもキャッシュの恩恵を得たままページを表示させることが可能のようです。

    下記図はデータのキャッシュの有無と Dynamic Function の使用の有無でルートのレンダリングが静的なルートになるのか動的なルートになるのかを示したものです。

    Dynamic FunctionsDataRoute
    使用しないキャッシュする静的なルート
    使用するキャッシュする動的なルート
    使用しないキャッシュしない動的なルート
    使用するキャッシュしない動的なルート

    この図から分かる通り、データのキャッシュもされており、Dynamic Function も使用していないルートだけが静的なルート、つまり Full Route Cache としてビルド時にキャッシュされるルートということになります。

    キャッシュの再検証

    Full Route Cache の再検証方法は二つあります。

    • Data Cache を再検証
      Full Route Cache でキャッシュされているルート内で、キャッシュされているデータ( Data Cache )を再検証することでルートがリレンダリングされ、その結果が新たなキャッシュとして保存されます。
    • 再ビルドを行う
      ビルド間で引き継がれる Data Cache とは違い、再ビルドすることで新たなキャッシュとして保存されます。

    キャッシュの無効化

    Full Route Cache は静的にレンダリングされたルートのみに適用されるため、下記のいずれかの方法で動的なレンダリングにすることで無効化することができます。

    • Dynamic Function を使用する
    • dynamic = 'force-dynamic' または revalidate = 0 を Route Config Option に使用する。
    • fetch したデータをキャッシュしない( Data Cache を利用しない)

    Router Cache

    Router Cache とはクライアント側で行われるキャッシュのことです。

    ユーザーがページ遷移をしたルート、または( <Link /> コンポーネントなどの使用により)Next.js により prefetch された各ルートの React Server Component Payload がブラウザ上に保存されます。

    このキャッシュにより、ブラウザバック・フォワードでの高速なページ遷移が可能になり、ページ遷移間でのReactの状態・ブラウザの状態が保存されます。

    Router Cache の流れ

    ( Next.js 公式サイトから抜粋 )

    クライアント側で保存される Full Route Cache のように感じられますが、Full Route Cache とは違い動的なルートもキャッシュされます。各ルートは下記の間隔でキャッシュされ、時間が経過すると対象のルートのキャッシュのみ削除されます。

    • 動的なルート: 30秒
      prefetch={true} または  router.prefetch を使用することでキャッシュ時間を5分にすることができます。)
    • 静的なルート: 5分

    キャッシュの再検証

    • Server Action を使用した方法
      • revalidatePath または revalidateTag を使用して、オンデマンドにキャッシュを削除する
      • cookies.set または cookies.delete の使用で、Cookie の更新によりルートが古くなるのを防ぐためキャッシュが削除される
    • router.refresh を使用することで、新しいリクエストをサーバーに送る

    キャッシュの無効化

    上記の方法でキャッシュを削除し再作成することは可能ですが、Router Cache の無効化は不可能です。

    また、<Link /> コンポーネントに prefetch={false} を使用することで、prefetch を無効化することができます(今までの Next.js の機能)。ただ prefetch を無効化した場合でも、ページ遷移をすることで30秒の間訪れたルートのキャッシュが保存されます。

    最後に

    Next.js のキャッシュをざっと見てきましたが、色々と各用途に合わせたキャッシュの種類があり、うまく使用することができればパフォーマンス向上の道具として大変有効に活用できそうだなと感じた反面、正しく理解して使用しないと想定しない挙動を招く恐れがあるなとも感じました。

    (キャッシュの使用に困ったら、頑張ってまとめたこの記事に戻って理解の助けとしたいと思います。ジブンエライ!)

    また、Next.js のキャッシュのことについて調べる中で、React Server Component Payload という単語が沢山出現しており、これを理解しないと Component のサーバー・クライアント間でのキャッシュを含めたレンダリングの理解が深まらないなと感じましたので、また別の記事に調べてまとめみようかと思います。

    ではではまた次の記事でお会いしましょう!

    モーリー(エンジニア)

    モーリー(エンジニア)

    おすすめ記事