コンテンツにスキップ
アプリケーションの構築データフェッチングインクリメンタル静的再生成 (ISR)

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

インクリメンタル静的再生成 (ISR) を使用すると、次のことが可能になります。

  • サイト全体を再構築せずに静的コンテンツを更新する
  • ほとんどのリクエストに対してプリレンダリングされた静的ページを提供することでサーバー負荷を軽減する
  • 適切なcache-controlヘッダーがページに自動的に追加されるようにする
  • 長いnext build時間なしで大量のコンテンツページを処理する

ここに最小限の例を示します

pages/blog/[id].tsx
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) },
  }))
 
  // We'll prerender only these paths at build time.
  // { fallback: 'blocking' } will server-render pages
  // on-demand if the path doesn't exist.
  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 will invalidate the cache when a
    // request comes in, at most once every 60 seconds.
    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ルートへの不正アクセスを防ぐために使用されます。

pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next'
 
export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  // Check for secret to confirm this is a valid request
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }
 
  try {
    // This should be the actual path not a rewritten path
    // e.g. for "/posts/[id]" this should be "/posts/1"
    await res.revalidate('/posts/1')
    return res.json({ revalidated: true })
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating')
  }
}

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

未処理の例外の処理

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

pages/blog/[id].tsx
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 }
}) => {
  // If this request throws an uncaught error, Next.js will
  // not invalidate the currently shown page and
  // retry getStaticProps on the next request.
  const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
  const post: Post = await res.json()
 
  if (!res.ok) {
    // If there is a server error, you might want to
    // throw an error instead of returning so that the cache is not updated
    // until the next successful request.
    throw new Error(`Failed to fetch posts, received status ${res.status}`)
  }
 
  return {
    props: { post },
    // Next.js will invalidate the cache when a
    // request comes in, at most once every 60 seconds.
    revalidate: 60,
  }
}

キャッシュの場所をカスタマイズする

ページをキャッシュし、再検証する (インクリメンタル静的再生成を使用) 際は、同じ共有キャッシュを使用します。Vercelにデプロイするすると、ISRキャッシュは自動的に永続ストレージに永続化されます。

自己ホスティングの場合、ISRキャッシュはNext.jsサーバーのファイルシステム(ディスク上)に保存されます。これは、Pages RouterとApp Routerの両方を使用して自己ホスティングする際に自動的に機能します。

キャッシュされたページとデータを永続ストレージに保持したり、複数のコンテナまたは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を使用してください。

バージョン履歴

バージョン変更点
v14.1.0カスタムcacheHandlerは安定版になりました。
v13.0.0App Routerが導入されました。
v12.2.0Pages Router: オンデマンドISRが安定版になりました
v12.0.0Pages Router: ボット認識ISRフォールバックが追加されました。
v9.5.0Pages Router: 安定版ISRが導入されました