はじめに/ガイド/ISR

インクリメンタル静的再生成 (ISR) の実装方法

インクリメンタル静的再生成 (ISR) を使用すると、以下のことが可能です:

  • サイト全体を再ビルドせずに静的コンテンツを更新
  • 事前レンダリングされた静的ページを提供することでサーバー負荷を軽減
  • 適切な cache-control ヘッダーが自動的にページに追加されることを保証
  • 大量のコンテンツページを next build の長時間化なしに処理

以下は最小限の例です:

import type { GetStaticPaths, GetStaticProps } from 'next'

interface Post {
  id: string
  title: string
  content: string
}

interface Props {
  post: Post
}

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await fetch('https://api.vercel.app/blog').then((res) =>
    res.json()
  )
  const paths = posts.map((post: Post) => ({
    params: { id: String(post.id) },
  }))

  // ビルド時にこれらのパスのみ事前レンダリング
  // { fallback: 'blocking' } は存在しないパスをオンデマンドでサーバーサイドレンダリング
  return { paths, fallback: false }
}

export const getStaticProps: GetStaticProps<Props> = async ({
  params,
}: {
  params: { id: string }
}) => {
  const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
    (res) => res.json()
  )

  return {
    props: { post },
    // Next.js はリクエストが来たときにキャッシュを無効化します(最大60秒に1回)
    revalidate: 60,
  }
}

export default function Page({ post }: Props) {
  return (
    <main>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </main>
  )
}

この例の動作:

  1. next build 時に既知のブログ投稿(この例では25件)が生成
  2. これらのページ(例: /blog/1)へのリクエストはキャッシュされ即時表示
  3. 60秒経過後、次のリクエストではキャッシュされた(古い)ページが表示
  4. キャッシュが無効化され、バックグラウンドでページの新しいバージョン生成開始
  5. 生成成功後、Next.js は更新されたページを表示してキャッシュ
  6. /blog/26 がリクエストされると、Next.js はオンデマンドでこのページを生成してキャッシュ

リファレンス

関数

res.revalidate() を使ったオンデマンド検証

より精密な再検証方法として、API ルーターから res.revalidate を使用してオンデマンドで新しいページを生成できます。

例えば、この API ルートは /api/revalidate?secret=<token> で呼び出され、指定されたブログ投稿を再検証します。Next.js アプリのみが知る秘密トークンを作成してください。この秘密は再検証 API ルートへの不正アクセスを防ぐために使用されます。

import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // 有効なリクエストか確認するために秘密をチェック
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: '無効なトークン' })
  }

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

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

未捕捉例外の処理

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

import type { GetStaticProps } from 'next'

interface Post {
  id: string
  title: string
  content: string
}

interface Props {
  post: Post
}

export const getStaticProps: GetStaticProps<Props> = async ({
  params,
}: {
  params: { id: string }
}) => {
  // このリクエストで未捕捉エラーが発生した場合、Next.js は
  // 現在表示されているページを無効化せず、
  // 次のリクエストで getStaticProps を再試行します
  const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
  const post: Post = await res.json()

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

  return {
    props: { post },
    // Next.js はリクエストが来たときにキャッシュを無効化します(最大60秒に1回)
    revalidate: 60,
  }
}

キャッシュロケーションのカスタマイズ

キャッシュされたページとデータを永続ストレージに保持したり、Next.js アプリケーションの複数のコンテナやインスタンス間でキャッシュを共有したい場合、Next.js キャッシュロケーションを設定できます。詳細を学ぶ

トラブルシューティング

ローカル開発でのキャッシュデータのデバッグ

fetch API を使用している場合、どのリクエストがキャッシュされているかどうかを理解するために追加のログを追加できます。logging オプションについて詳しく学ぶ

next.config.js
module.exports = {
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}

本番環境での正しい動作確認

本番環境でページが正しくキャッシュされ、再検証されていることを確認するには、ローカルで next build を実行した後に next start を実行して本番用Next.jsサーバーを起動します。

これにより、本番環境と同様のISR(インクリメンタル静的再生成)の動作をテストできます。さらにデバッグを行うには、.env ファイルに以下の環境変数を追加してください:

.env
NEXT_PRIVATE_DEBUG_CACHE=1

この設定により、Next.jsサーバーはISRキャッシュのヒットとミスをコンソールにログ出力します。next build 時にどのページが生成されたか、またオンデマンドでパスにアクセスした際にページがどのように更新されるかを確認できます。

注意点

  • ISRはNode.jsランタイム使用時(デフォルト)のみサポートされます
  • 静的エクスポート作成時にはISRはサポートされません
  • オンデマンドISRリクエストではミドルウェアは実行されません。つまり、パス書き換えやミドルウェア内のロジックは適用されません。正確なパスを再検証していることを確認してください。例: 書き換えられた/post-1ではなく/post/1

プラットフォームサポート

デプロイ方法サポート状況
Node.jsサーバーはい
Dockerコンテナはい
静的エクスポートいいえ
アダプタープラットフォーム依存

Next.jsをセルフホスティングする際のISR設定方法について学べます。

バージョン履歴

バージョン変更内容
v14.1.0カスタムcacheHandlerが安定版に
v13.0.0App Routerが導入
v12.2.0Pages Router: オンデマンドISRが安定版に
v12.0.0Pages Router: ボット対応ISRフォールバック追加
v9.5.0Pages Router: 安定版ISR導入