🚀

やっとわかる!Next.js App Routerのcacheにおけるrevalidateと苦戦した話

2023/11/06に公開

こんにちは。ココナラテックエージェントの開発をしているエンジニアのみんです。

新技術が常に登場しているフロントエンドの開発で、時代の波に乗り、キャッチアップすることが我々エンジニアの使命です。

ココナラテックエージェントのフロントエンドは Next.js アプリケーションです。2023年の9月から、App Routerへの移行を始めています。
つい9月末に、ココナラテックエージェントの案件一覧ページApp Routerに移行しました!

案件一覧と検索を含め、案件まわりのキャッシュ再検証(revalidate)の実装に全身全霊を注いだ成果をシェアしたくて、この記事を書きました。

App Routerのキャッシュ種類

Next.jsは、レンダリングの効率を上げるために、オプション指定がない場合に全てのリクエスト結果をキャッシュする方針です。

公式ドキュメントによると、App Routerのキャッシュは以下の4種類があります。

  • Request Memoization
  • Data Cache
  • Full Route Cache
  • Router Cache

Request Memoization

React自体の機能です。コンポーネントツリーから、url先とオプション設定が同様のリクエストは同一のリクエストとみなして一回だけリクエストします。

これを利用することで、複数リクエストが自動的に整理され、ルートコンポーネントでデータを一気にリクエストしてチャイルドコンポーネントにpropsを渡す構造を回避できます。

この種類のキャッシュはレンダリングだけに適用し、他のリクエストと共有されていないので、再検証の必要はないです。

Data Cache

fetchAPI経由で取得したデータはキャッシュされてData Cacheと呼ばれます。

この種類のキャッシュは再リクエストしても、もしくはアプリケーションを再デプロイしても持続するので、リセットしたい場合は再検証をしなければなりません。

Full Route Cache

アプリケーションをビルドした時に、ルートのレンダリング結果がキャッシュされます。

この種類のキャッシュはアプリケーションを再デプロイしたら消えますが、強制再検証したい時はData Cacheを再検証するとリレンダリングされます。

Router Cache

クライアントサイドのキャッシュです。ページ遷移する度に目標ページ内のレイアウトとページコンポーネントをキャッシュし、キャッシュがない部分だけを更新してスムーズに遷移することができます。

この種類のキャッシュは、ページをリロード、もしくは5分ごとに自動的に再検証されますが、必要な時に強制再検証も適用できます。

再検証を実装

再検証は、以下の2種類に分けることができます。

  • time-base revalidation
  • on-demand revalidation

time-base revalidation

名前通りに時間間隔で再検証します。fetchする時にキャッシュの保存時間を指定します。

// キャッシュの保存時間を3600秒(1時間)に指定
fetch('https://...', { next: { revalidate: 3600 } })

引用:Time-based Revalidation

保存時間が経過以後の一番目のリクエストは旧キャッシュで返しますが、新たなデータを更新して、2番目以降のリクエストは新たなキャッシュデータになります!

on-demand revalidation

時間間隔ではなく、再検証が必要な時にキャッシュデータをパージします。

  • revalidatePath:パスごとで再検証
  • revalidateTag:タグごとで再検証

適用する場面は2つあります。

  • Server Actionで使います。(アプリケーション内の処理を対応、例えばフォームの保存か更新の処理が成功したら、相応のキャッシュデータをパージします)
  • Route Handlerで使います。(アプリケーション外の処理を対応、エンドポイントを提供して、実行する時に相応のキャッシュデータをパージします。)

今度はRoute Handlerを採用して、ココナラテックエージェントのAPI側で案件が更新される度に、フロントエンドの案件再検証のエンドポイントを叩いて案件のキャッシュデータをパージします。

revalidatePathの使用例

import { revalidatePath } from 'next/cache'

// 特定のurlを再検証
revalidatePath('/blog/post-1')

// パスを再検証
revalidatePath('/blog/[slug]', 'page')

引用:revalidatePath

revalidateTagの使用例

fetch側の実装

Ln.45の実装で、fetchリクエストのオプションにキャッシュタグを指定します。

https://github.com/vercel/next.js/blob/ab7b0f59fb0d793fb4ee593ae93f7e08bab23b5b/examples/cms-contentful/lib/api.ts#L31-L48

Route Handler側の実装

キャッシュタグを再検証します。

https://github.com/vercel/next.js/blob/ab7b0f59fb0d793fb4ee593ae93f7e08bab23b5b/examples/cms-contentful/app/api/revalidate/route.ts

on-demand revalidationの実装上の注意点

Magic code doesn't always work, but engineers have to.
魔法のようなコードはいつもうまく効くわけではありませんが、エンジニアたちはやらなければなりません。

By みん

on-demand revalidationの実装はrevalidatePathrevalidateTagコード一行だけで、一見かなりシンプルで魔法のようですが、実際にいくつかの罠にハマりました。

Vercelの使用者認証

ステージング環境で、社内メンバーを確認するため使用者認証をかけています。そのためキャッシュ再認証のリクエストが認可できず、401エラーになりました。
使用者認証を突破するため、再認証Route HandlerのHTTPメソッドをOPTIONにしました。

参考:Vercel Authentication Considerations

Next.jsを特定バージョンに上げないといけない可能性があります

下記の討論により、Next.jsのバージョンを13.5.4に上げないとrevalidateTagがうまく動作しない可能性があります。

https://github.com/vercel/next.js/issues/55960

サイト機能をテストした上で、ココナラテックエージェントはNext.jsのバージョンを13.5.4に上げました!

既存キャッシュが残って一回パージしないといけないことがあり得ます

既存キャッシュがどうしてもリセットできない場合は、パージしかないです。
Vercelコンソールから、赤い「Purge Everything」ボタンを大胆に押しましょう。

参考:Vercel Purging Data Cache

最後に

日々進化しているココナラテックエージェントのサービス、小さい開発チームですが、新技術に挑戦できる環境です。

今回の話に興味を持っていただけた方は是非ご応募ください!

https://coconala.co.jp/recruit/engineer/

Discussion