インクリメンタル静的再生成 (ISR)

Next.js では、サイトをビルドしたに静的ページを作成または更新できます。インクリメンタル静的再生成 (ISR) を使用すると、ページごとに静的生成を利用でき、サイト全体を再ビルドする必要がありません。ISR を利用することで、静的生成の利点を維持しながら数百万ページにスケールできます。

補足: edge ランタイム は現在 ISR と互換性がありませんが、cache-control ヘッダーを手動で設定することで stale-while-revalidate を活用できます。

ISR を使用するには、getStaticPropsrevalidate プロパティを追加します:

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// この関数はビルド時にサーバーサイドで呼び出されます。
// 再検証が有効で新しいリクエストがある場合、
// サーバーレス関数で再度呼び出される可能性があります
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js はページの再生成を試みます:
    // - リクエストがあったとき
    // - 最大10秒に1回
    revalidate: 10, // 秒単位
  }
}

// この関数はビルド時にサーバーサイドで呼び出されます。
// パスが生成されていない場合、サーバーレス関数で
// 再度呼び出される可能性があります
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // 投稿に基づいてプリレンダリングするパスを取得
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // ビルド時にこれらのパスのみをプリレンダリングします。
  // { fallback: 'blocking' } は、パスが存在しない場合、
  // オンデマンドでページをサーバーレンダリングします
  return { paths, fallback: 'blocking' }
}

export default Blog

ビルド時にプリレンダリングされたページにリクエストがあると、最初はキャッシュされたページが表示されます。

  • 初期リクエスト後10秒以内のすべてのリクエストもキャッシュされ、即時に表示されます。
  • 10秒のウィンドウ後、次のリクエストでは依然としてキャッシュされた(古い)ページが表示されます
  • Next.js はバックグラウンドでページの再生成をトリガーします。
  • ページの生成が成功すると、Next.js はキャッシュを無効化し、更新されたページを表示します。バックグラウンドでの再生成が失敗した場合、古いページは変更されません。

生成されていないパスへのリクエストがあると、Next.js は最初のリクエストでページをサーバーレンダリングします。以降のリクエストではキャッシュから静的ファイルが提供されます。Vercel 上の ISR はキャッシュをグローバルに永続化し、ロールバックを処理します

補足: 上流のデータプロバイダーがデフォルトでキャッシュを有効にしているか確認してください。useCdn: false のように無効にする必要があるかもしれません。そうしないと、再検証で新しいデータを取得して ISR キャッシュを更新できません。Cache-Control ヘッダーが返されると、CDN でキャッシュが発生する可能性があります(リクエストされているエンドポイントに対して)。

オンデマンド再検証

revalidate 時間を 60 に設定すると、すべての訪問者は1分間同じ生成バージョンのサイトを見ます。キャッシュを無効にする唯一の方法は、1分経過後に誰かがそのページを訪問することです。

v12.2.0 から、Next.js はオンデマンドインクリメンタル静的再生成をサポートし、特定のページの Next.js キャッシュを手動でパージできます。これにより、以下の場合にサイトを更新しやすくなります:

  • ヘッドレス CMS からのコンテンツが作成または更新されたとき
  • 電子商取引のメタデータが変更されたとき(価格、説明、カテゴリ、レビューなど)

getStaticProps 内で、オンデマンド再検証を使用するために revalidate を指定する必要はありません。revalidate が省略されている場合、Next.js はデフォルト値 false(再検証なし)を使用し、revalidate() が呼び出されたときにのみオンデマンドでページを再検証します。

補足: ミドルウェア はオンデマンド ISR リクエストに対して実行されません。代わりに、再検証したい正確なパスで revalidate() を呼び出してください。たとえば、pages/blog/[slug].js があり、/post-1 から /blog/post-1 へのリライトがある場合、res.revalidate('/blog/post-1') を呼び出す必要があります。

オンデマンド再検証の使用

まず、Next.js アプリのみが知る秘密トークンを作成します。この秘密は、再検証 API ルートへの不正アクセスを防ぐために使用されます。以下の URL 構造でルートにアクセスできます(手動またはウェブフック経由):

ターミナル
https://<your-site.com>/api/revalidate?secret=<token>

次に、秘密をアプリケーションの環境変数として追加します。最後に、再検証 API ルートを作成します:

pages/api/revalidate.js
export default async function handler(req, res) {
  // 秘密をチェックして有効なリクエストか確認
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: '無効なトークン' })
  }

  try {
    // これはリライトされたパスではなく実際のパスである必要があります
    // 例: "/blog/[slug]" の場合は "/blog/post-1"
    await res.revalidate('/path-to-revalidate')
    return res.json({ revalidated: true })
  } catch (err) {
    // エラーが発生した場合、Next.js は最後に正常に生成された
    // ページを表示し続けます
    return res.status(500).send('再検証エラー')
  }
}

デモを確認してオンデマンド再検証の動作を確認し、フィードバックを提供してください。

開発中のオンデマンド ISR テスト

next dev でローカル実行する場合、getStaticProps はすべてのリクエストで呼び出されます。オンデマンド ISR 設定が正しいことを確認するには、プロダクションビルド を作成し、プロダクションサーバー を起動する必要があります:

ターミナル
$ next build
$ next start

その後、静的ページが正常に再検証されたことを確認できます。

エラー処理と再検証

バックグラウンド再生成中に getStaticProps 内でエラーが発生した場合、または手動でエラーをスローした場合、最後に正常に生成されたページが表示され続けます。次のリクエストで、Next.js は getStaticProps の呼び出しを再試行します。

export async function getStaticProps() {
  // このリクエストでキャッチされないエラーが発生した場合、Next.js は
  // 現在表示されているページを無効化せず、
  // 次のリクエストで getStaticProps を再試行します
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  if (!res.ok) {
    // サーバーエラーがある場合、キャッシュが更新されないように
    // 返す代わりにエラーをスローすることを検討してください
    // 次に成功するリクエストまで
    throw new Error(`投稿の取得に失敗しました、ステータス ${res.status}`)
  }

  // リクエストが成功した場合、投稿を返し
  // 10秒ごとに再検証します
  return {
    props: {
      posts,
    },
    revalidate: 10,
  }
}

セルフホスティング ISR

インクリメンタル静的再生成 (ISR) は、next start を使用する場合、セルフホスティング Next.js サイト でそのまま動作します。

KubernetesHashiCorp Nomad などのコンテナオーケストレーターにデプロイする場合、このアプローチを使用できます。デフォルトでは、生成されたアセットは各ポッドのメモリ内に保存されます。つまり、各ポッドには静的ファイルの独自のコピーがあります。特定のポッドがリクエストを受けるまで、古いデータが表示される可能性があります。

すべてのポッド間で一貫性を確保するには、メモリ内キャッシュを無効にできます。これにより、Next.js サーバーはファイルシステム内の ISR によって生成されたアセットのみを利用するようになります。

Kubernetes ポッド(または類似の設定)で共有ネットワークマウントを使用すると、異なるコンテナ間で同じファイルシステムキャッシュを再利用できます。同じマウントを共有することで、next/image キャッシュを含む .next フォルダも共有され、再利用されます。

メモリ内キャッシュを無効にするには、next.config.js ファイルで isrMemoryCacheSize0 に設定します:

next.config.js
module.exports = {
  experimental: {
    // デフォルトは50MB
    isrMemoryCacheSize: 0, // バイト単位のキャッシュサイズ
  },
}

補足: 共有マウントの設定方法によっては、複数のポッドが同時にキャッシュを更新しようとする競合状態を考慮する必要があるかもしれません。

バージョン履歴

バージョン変更点
v12.2.0オンデマンド ISR が安定版に
v12.1.0オンデマンド ISR が追加(ベータ)。
v12.0.0ボット対応 ISR フォールバック が追加。
v9.5.0ベースパスが追加。