Next.16で改善されたキャッシュAPIを調査したんじゃ
初めに
気づいたら11月、今年ももう少しで終わりですね〜
どうでもいいんですけど、Fischer’sの増量中を会社でやりたいなと密かに思っています。(絶対にここで書く事ではないのだけど)
さて今回はNext.16で改善されたキャッシュAPI主にrevalidateTag・updateTag・refreshの使い分けや使い方を詳しく見ていこうと思います。
revalidateTag:キャッシュデータのタグ無効化と再検証
revalidateTagは指定したタグに紐づくキャッシュデータをオンデマンドで「無効化(再検証対象にする)」する関数です。主に多少の遅延が許容されるコンテンツに適しており、データをすぐ最新化する必要がないケース(ブログ記事、商品カタログ、ドキュメントなど)で利用するのが一般的です。
無効化後、ユーザーには現在のキャッシュ(古いデータ)が提供されつつ、バックグラウンドで新しいデータのフェッチ・更新が行われます。
使用できる場所
サーバーサイドのコンテキストである Server Action(サーバーアクション)や Route Handler(APIルート)内部で呼び出せます。クライアントコンポーネント内や直接ブラウザ上では使用できません。
使い方
デフォルトでは第二引数にキャッシュのライフタイムプロファイルmaxを指定して使用します。このprofile=maxを付けることで stale-while-revalidate が有効になり、**キャッシュを即座に削除せず「古いデータを見せながら裏で再取得」**する動作になります。ユーザーには遅延なく旧データが表示され、更新完了後に新データへ切り替わります。
第二引数を省略すると即時にキャッシュを無効化します(次のリクエスト時に強制的に再フェッチ・再レンダリングを行う)ですが、この一引数のみの使用法は非推奨になりました。Next.js 16では、即時再検証が必要な場合は後述のupdateTagへの移行が推奨されています。
タグ付きキャッシュデータを扱うため、あらかじめfetch関数のオプションnext: { tags: [...] }でタグを付与するか、'use cache'指令を用いる関数内でcacheTag('tagName')を指定してデータにタグ付けしておく必要があります。
使用例
新規ユーザー情報更新後に該当データのタグを再検証(無効化)する例です。データ変更後にrevalidateTag('user', 'max')を呼び出すことで、タグ"user"に関連するあらゆるページのキャッシュを stale 状態にし、次回アクセス時にバックグラウンドで最新データへ更新させます(ユーザーには旧データが瞬時に表示され続け、更新完了後に新データに差し替わります)。
簡易バージョン
'use server';
import { revalidateTag } from 'next/cache';
export async function updateUser(id: string, newData: UserData) {
// 1. データを更新(例: データベースの更新)
await db.user.update(id, newData);
// 2. 関連キャッシュを再検証(「user」タグのデータを stale 状態にする)
revalidateTag('user', 'max');
}
個人的にはrevalidateTagを使用するなら後で記載するupdateTagを使用するかなと思います。revalidateTagがデータをすぐに最新化する必要がない時に使用するとは書きましたが、最新の方がいいんじゃん?と思っています。
updateTag:即時のキャッシュ無効化(Read-Your-Own-Writes 対応)
updateTagは Next.js 16で新たに導入された、サーバーアクション内専用のキャッシュ無効化関数です。特定のタグに紐づくデータのキャッシュを即座に期限切れにし、次のリクエストで必ず最新データを取得させるために使います。ユーザーがデータを投稿・更新した直後にその変更が画面に即反映される、「自分の行った変更を自分で直ちに確認できる」(read-your-own-writes)ための機能です。revalidateTagと異なり古いデータを見せる猶予を設けず、同じリクエスト中でデータを更新・再取得してUIに反映する動作を行います。
使用できる場所
くどいですがServer Action内でのみ利用可能です。Route Handler(APIルート)やクライアント側から呼ぶことはできず、もしServer Action外で呼び出すとエラーになります。Server Action内で動作させることで、Next.jsはキャッシュをただ無効にするだけでなく必要な再フェッチも即座に実行し、画面を更新します。
使い方
指定したタグのキャッシュエントリを即時に期限切れ(expired)状態にします。これにより次回そのタグ付きデータを要求する際は必ず新しいデータの取得が行われ、古いキャッシュは使用されません。言い換えれば、updateTag実行後の最初の再レンダリングは必ずブロッキングして最新データを取りに行き、ユーザーには更新済みの内容が表示されます。そのためユーザーのフォーム送信や設定変更など即時反映が求められるUI操作に向いています。
使用例
下記はブログ記事の投稿作成アクション内で、記事一覧と詳細ページ用のキャッシュを即時無効化してからリダイレクトする例です。updateTag('posts')により記事一覧(タグ:"posts")のキャッシュを失効させ、updateTag(\post-${post.id})で新規投稿個別ページ(タグ例:"post-123")のキャッシュも失効させています。こうすることで、リダイレクト後に遷移したページや戻った一覧ページでユーザーが古いキャッシュを見ることなく最新投稿が表示されます。
'use server';
import { updateTag } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
// 1. 新規ポストをデータベースに作成
const post = await db.post.create({
data: {
title: formData.get('title'),
content: formData.get('content')
}
});
// 2. 関連するキャッシュを即時無効化(次回アクセス時に最新データを取得)
updateTag('posts'); // 投稿一覧ページ用のタグを無効化
updateTag(`post-${post.id}`); // 個別投稿ページ用のタグを無効化
// 3. 別ページに遷移(遷移先で最新の投稿データが表示される)
redirect(`/posts/${post.id}`);
}
ページの再描画(クライアントルーターのリフレッシュ)
refresh は現在のページ(ルート)を再度読み込み(再レンダリング)するための関数です。Server Actionから呼び出すことで、クライアント側のNext.jsルーターに対してそのページのデータを再取得・再描画するよう指示します。言い換えると、ブラウザの手動リロードを行わずにNext.jsアプリ内でプログラム的にページ更新(router.refresh 相当)を行う手段です。
使用できる場所
こちらも Server Action内 でのみ使用可能で、それ以外(Route Handlerやクライアントコンポーネント等)で呼ぶとエラーになります。
使い方
refresh()を呼ぶと、Next.jsは現在表示中のページ全体に対して再度データフェッチと再レンダリングを実行します。例えばそのページがServer ComponentによってSSRで描画されていれば、そのServer Componentのデータ取得処理が再実行され、最新の内容で画面が更新されます。クライアント側の状態(例えばフォームの入力値やUIの一時状態)は維持されたまま、サーバー由来のデータだけが更新されるようになっています。これはNext.jsのrouter.refresh()と同様、Reactのクライアント状態は再マウントせずサーバーコンポーネント部分のみを再読み込みする仕組みによります。
例えば通知の「既読」をデータベースで更新した後、ヘッダーに表示している未読通知数を最新化するためにrefresh()を呼ぶケースが挙げられます。他にも、フォーム送信後も同じページに留まりつつ内容だけ更新したい場合など、ページ全体をリロードせず動的部分だけ更新したい状況で有用です。
使用例
下記は通知を既読にするServer Action内でrefresh()を利用する例です。データ更新後にrefresh()を呼ぶことで、ユーザーの現在閲覧中ページ(例としてヘッダーの通知数を含むレイアウト)のデータが再取得され、画面上の通知数表示が最新状態に更新されます。
'use server';
import { refresh } from 'next/cache';
export async function markNotificationAsRead(notificationId: string) {
// 1. 通知をデータベース上で既読に更新
await db.notifications.markAsRead(notificationId);
// 2. ページを再描画(通知件数など表示中のデータを最新化)
refresh();
}
まとめ
サーバーアクション内でユーザー操作により即座にUIを更新したいなら updateTag(特定データだけ更新)か refresh(ページ全体を再読み込み)を使用します。
外部からのリクエストや非アクションのコンテキストでは updateTagは使えないため、代わりに revalidateTag を使用します。特にWebhook経由で即時更新したい場合は revalidateTag(tag, { expire: 0 }) のように即時失効させることも可能です。
遅延許容の範囲や更新したいデータの粒度に応じて、revalidateTag (遅延更新・部分更新) と updateTag (即時更新・部分更新) を使い分けます。タグ管理が難しい場合や画面全体を再取得したい場合は refresh でリフレッシュする、といった具合に使い分けると良いでしょう。
Discussion