Menu

Incremental Static Regeneration (ISR)の実装方法

Incremental Static Regeneration(ISR)により、以下が実現できます:

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

最小限の例を以下に示します:

pages/blog/[id].tsx
TypeScript
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) },
  }))
 
  return { paths, fallback: 'blocking' }
}
 
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中に、すべての既知のブログ記事が生成されます
  2. これらのページへのすべてのリクエスト(例:/blog/1)がキャッシュされ、即座に返されます
  3. 60秒経過後、次のリクエストはキャッシュされた(古い)ページを返します
  4. キャッシュが無効化され、ページの新しいバージョンがバックグラウンドで生成され始めます
  5. 正常に生成されたら、次のリクエストは更新されたページを受け取り、後続のリクエスト用にキャッシュされます
  6. /blog/26がリクエストされ、それが存在する場合、ページはオンデマンドで生成されます。この動作は別のfallback値を使用することで変更できます。ただし、記事が存在しない場合は404が返されます。

リファレンス

関数

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

より正確な再検証方法のために、API Routerからres.revalidateを使用してオンデマンドで新しいページを生成します。

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

pages/api/revalidate.ts
TypeScript
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: 'Invalid token' })
  }
 
  try {
    // これは書き直されたパスではなく実際のパスである必要があります
    // 例えば、「/posts/[id]」の場合、これは「/posts/1」である必要があります
    await res.revalidate('/posts/1')
    return res.json({ revalidated: true })
  } catch (err) {
    // エラーがあった場合、Next.jsは最後に正常に生成されたページを
    // 引き続き表示します
    return res.status(500).send('Error revalidating')
  }
}

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

キャッチされていない例外の処理

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

pages/blog/[id].tsx
TypeScript
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(`Failed to fetch posts, received status ${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はStatic Exportを作成する場合はサポートされません。
  • プロキシはオンデマンドISRリクエストに対して実行されません。つまり、プロキシ内のパス書き直しまたはロジックは適用されません。正確なパスを再検証していることを確認します。例えば、書き直された/post-1ではなく/post/1です。

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

デプロイメントオプションサポート状況
Node.jsサーバーあり
Dockerコンテナあり
Static exportなし
Adaptersプラットフォーム依存

Next.jsを自己ホストする場合のISRの設定方法を学習してください。

バージョン履歴

バージョン変更内容
v14.1.0カスタムcacheHandlerが安定化。
v13.0.0App Routerが導入。
v12.2.0Pages Router:On-Demand ISRが安定化。
v12.0.0Pages Router:Bot-aware ISR fallbackを追加。
v9.5.0Pages Router:Stable ISRが導入