Open3

Incremental Static Regeneration (ISR)

あおけんあおけん

Next.jsでは、サイト構築後に静的ページを作成または更新できる。
インクリメンタル・スタティック・リジェネレーション(ISR)により、
サイト全体を再構築することなく、ページ単位でスタティック・ジェネレーションを使用できる。
ISRを使用することで、静的生成の利点を維持しながら、
数百万ページまで拡張することができる。

cache-controlヘッダーを手動で設定することで、
stale-while-revalidateを活用することはできるが、
エッジランタイムは現在のところISRと互換性がない。

ISRを使用するには、getStaticPropsにrevalidateプロップを追加。

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
 
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}
 
// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()
 
  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))
 
  // We'll pre-render only these paths at build time.
  // { fallback: 'blocking' } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' }
}
 
export default Blog

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

  • 最初のリクエストの後、10秒前までのページへのリクエストもキャッシュされ、瞬時に処理される
  • 10秒のウィンドウの後、次のリクエストはキャッシュされた(古くなった)ページを表示
  • Next.jsはバックグラウンドでページの再生をトリガーする
  • ページが正常に生成されると、Next.jsはキャッシュを無効にして更新されたページを表示
    • バックグラウンド再生成が失敗した場合、古いページは変更されないまま

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

アップストリーム・データ・プロバイダーがデフォルトでキャッシュを有効にしているか確認する。
無効にしないと(useCdn: falseなど)、再バリデーションでISRキャッシュを更新するために新しいデータを引き出すことができない。
キャッシュは、CDN が Cache-Control ヘッダを返したときに (リクエストされたエンドポイントに対して) 発生する。

あおけんあおけん

On-Demand Revalidation

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

v12.2.0から、Next.jsは特定のページのNext.jsキャッシュを、
手動でパージするOn-Demand Incremental Static Regenerationをサポートしている。
これにより、サイトの更新が簡単に。

  • ヘッドレスCMSのコンテンツが作成または更新される
  • Eコマースのメタデータの変更(価格、説明、カテゴリー、レビューなど)

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

オンデマンドISR要求では、ミドルウェアは実行されない。
代わりに、再検証してほしい正確なパスに対して revalidate() を呼び出す。
例えば、pages/blog/[slug].jsがあり、/post-1 -> /blog/post-1へ書き換える場合、res.revalidate('/blog/post-1')を呼び出す必要があります。

Using On-Demand Revalidation

まず、Next.jsアプリだけが知っている秘密のトークンを作成。
このシークレットは、再バリデーションAPIルートへの不正アクセスを防止するために使用。
このルートには、以下の URL 構造でアクセスできる (手動またはウェブフック)

https://<your-site.com>/api/revalidate?secret=<token>

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

// pages/api/revalidate.js
export default async function handler(req, res) {
  // 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 "/blog/[slug]" this should be "/blog/post-1"
    await res.revalidate('/path-to-revalidate')
    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')
  }
}

Testing on-Demand ISR during development

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

$ next build
$ next start

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

あおけんあおけん

Error handling and revalidation

getStaticPropsの内部でバックグラウンド再生を処理する際にエラーが発生した場合、
または手動でエラーをスローした場合、
最後に正常に生成されたページが表示され続ける。

次のリクエストで、Next.jsはgetStaticPropsの呼び出しを再試行する。

export async function getStaticProps() {
  // 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://.../posts')
  const posts = 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}`)
  }
 
  // If the request was successful, return the posts
  // and revalidate every 10 seconds.
  return {
    props: {
      posts,
    },
    revalidate: 10,
  }
}

Self-hosting ISR

インクリメンタル・スタティック・リジェネレーション(ISR)は、
next startを使用すると、セルフホスティングのNext.jsサイトですぐに機能する。