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サイトですぐに機能する。