8.

Next.js の再検証|revalidateTag と updateTag

編集

この記事の要点

  • キャッシュの寿命は cacheLife で決める。stalerevalidateexpire の 3 つの時間で構成される。
  • 時間ではなく出来事で捨てたい場合は cacheTag で目印を付け、更新時にそのタグを指定して無効化する。
  • revalidateTag は「古いものを見せつつ裏で更新」、updateTag は「今すぐ反映」。用途が違う。
  • バージョン 16 の revalidateTag第 2 引数が必須になった。1 引数の書き方は型エラーになる。
  • refresh はクライアント側のルーターを更新するだけで、タグ付きデータの再取得は行わない。

キャッシュを置いた瞬間から「いつ捨てるか」という問題が生まれます。長すぎれば古い情報が出続け、短すぎればキャッシュの意味がありません。Next.js はこれを「時間で切る」と「出来事で切る」の 2 系統で扱います。この記事では両者の使い分けと、バージョン 16 で追加・変更された API を整理します。

1時間で切る — cacheLife の 3 つの値

cacheLife'use cache' のスコープ内で呼び、そのキャッシュの寿命を決めます。指定するのは 3 つの時間です。

プロパティ意味効く場所
staleクライアントがサーバーに問い合わせずキャッシュを使える時間ブラウザ側のルーター
revalidateこの時間を過ぎた後の最初のリクエストで、裏側での作り直しが始まるサーバー
expireこの時間アクセスが無いと、次のリクエストは新しい内容ができるまで待つサーバー

この 3 つを毎回考えるのは大変なので、名前付きのプリセットが用意されています。

プロファイル想定する用途revalidateexpire
seconds株価・スコアなどのリアルタイム値1 秒1 分
minutesニュース・フィード1 分1 時間
hours在庫・天気など日に数回更新1 時間1 日
daysブログ記事1 日1 週間
weeks週次のコンテンツ1 週間30 日
max規約ページなど、ほぼ変わらないもの30 日1 年
default指定しなかった場合に適用される15 分無期限
// app/blog/[slug]/page.tsx
import { cacheLife } from 'next/cache'

export default async function BlogPost() {
  'use cache'
  cacheLife('days')          // ブログ記事は日次更新

  const post = await fetchBlogPost()
  return <article>{post.content}</article>
}

プリセットが合わない場合は、next.config.ts で独自の名前を定義するか、その場でオブジェクトを渡します。省略したプロパティは default から引き継がれます。

// next.config.ts
const nextConfig = {
  cacheComponents: true,
  cacheLife: {
    editorial: {
      stale: 600,        // 10 分
      revalidate: 3600,  // 1 時間
      expire: 86400,     // 1 日
    },
  },
}
export default nextConfig

cacheLife は毎回書くことが推奨されています。省略しても動きますが、入れ子になったキャッシュがあると、外側の寿命が内側に引っぱられて短くなることがあり、追いにくくなるためです。

2出来事で切る — cacheTag

「記事を編集したら、その記事のキャッシュだけを捨てたい」という要求は時間では表現できません。この場合はキャッシュにタグを付けます。

import { cacheLife, cacheTag } from 'next/cache'

async function getPost(slug: string) {
  'use cache'
  cacheLife('days')
  cacheTag(`post-${slug}`)     // このエントリの目印

  return await fetchPost(slug)
}

あとは更新処理の中で、そのタグを指定して無効化します。タグは複数付けられるので、「この記事」と「記事一覧」の両方を同時に狙うこともできます。

3revalidateTag と updateTag の違い

ここがバージョン 16 で最も間違えやすい部分です。似た名前の 2 つは、目的がはっきり分かれています。

revalidateTag 古いものを見せながら裏で更新 ① 更新処理でタグを指定 ② 次の閲覧者には まだ古い内容 ③ 裏側で作り直しが走る 記事・商品カタログ向き updateTag その場で捨てて即座に作り直す ① ユーザーが自分で更新 ② 同じリクエスト内で作り直す ③ 画面には新しい値が出る プロフィール編集・設定変更向き
自分が行った変更がその場で見えないと不自然な操作には updateTag、他人の閲覧に少し遅れて反映されればよいものには revalidateTag。

revalidateTag はバージョン 16 で第 2 引数が必須になりました。ここには cacheLife のプロファイル名を渡します。

// 旧: revalidateTag('posts')
revalidateTag('posts', 'max')

updateTag は Server Actions の中でのみ使える API で、キャッシュを失効させたうえで同じリクエストの中で作り直します。ユーザーが自分の変更をすぐ確認できる、いわゆる read-your-writes の挙動になります。

// app/actions.ts
'use server'

import { updateTag } from 'next/cache'

export async function updateUserProfile(userId: string, profile: Profile) {
  await db.users.update(userId, profile)
  updateTag(`user-${userId}`)   // 即座に反映される
}

4revalidatePath と refresh

タグを付けていない場合や、URL 単位でまとめて捨てたい場合は revalidatePath が使えます。これは内部的にはタグ機構の上に乗った便利関数で、そのパス用の特別なタグを無効化しています。

'use server'

import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  // データを更新する処理
  revalidatePath('/posts')
}

もう一つ、refresh という API があります。これはクライアント側のルーターを更新するだけで、タグ付きキャッシュの再取得は行いません。ヘッダーの通知件数を更新したい、といった軽い用途向けです。

使い分けの目安
特定データを狙うなら cacheTagrevalidateTag / updateTag。画面まるごとなら revalidatePath。表示の再取得だけなら refresh
やりがちな誤り
refresh を呼んだのに古い値が出続ける、という相談は多い。タグ付きデータには効かないので、updateTag を使う場面である可能性が高い。

5寿命の短さが描画に与える影響

寿命の指定は「いつ作り直すか」だけでなく、「事前生成に含められるかどうか」にも影響します。

  • revalidate が 0、または expire が 5 分未満 → 事前生成から外れ、リクエスト時に解決される
  • stale が 30 秒未満 → 先読みしても届く前に期限切れになるため、事前生成から外れる

プリセットの中では seconds だけがこの条件に当たります。リアルタイム性の高い部分を seconds にする場合は、Suspense で包んでフォールバックを用意しておくのが自然な構成になります。

6つまずきやすいところ

  • revalidateTag('posts') が型エラーになる:バージョン 16 では第 2 引数が必須です。
  • 更新したのに画面が変わらないrevalidateTag は古い内容を見せたまま裏で更新します。即時反映が必要なら updateTag です。
  • 複数インスタンスで反映されない:既定ではタグの無効化は呼ばれたインスタンスにしか効きません。複数台構成では共有ストアとタグ同期の仕組みが要ります(本番ビルドとセルフホスト)。
  • 入れ子のキャッシュで寿命が縮む:外側に cacheLife を明示していないと、内側の短い寿命に引っぱられます。外側にも必ず書きます。

読み取り側の設計はここまでです。次は書き込み、つまりフォーム送信とデータ更新に進みます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 開発環境の構築とプロジェクトの初期構成
  2. App Router のルーティングとファイル規約
  3. レイアウトと特殊ファイルの使い分け
  4. サーバーコンポーネントとクライアントコンポーネント
  5. サーバー側でのデータ取得と並列化
  6. ストリーミングで体感速度を上げる
  7. キャッシュの仕組みと use cache
  8. 再検証の使い分け
  9. フォーム送信とデータ更新の実装
  10. エラーハンドリングとエラー境界
  11. Route Handlers で API を作る
  12. メタデータ API で SEO を整える
  13. 画像とフォントの最適化
  14. 本番ビルドとセルフホスト
  15. バージョン16 への移行と注意点