インクリメンタル静的再生成 (ISR)
Next.js では、サイトをビルドした後に静的ページを作成または更新できます。インクリメンタル静的再生成 (ISR) を使用すると、ページごとに静的生成を利用でき、サイト全体を再ビルドする必要がありません。ISR を利用することで、静的生成の利点を維持しながら数百万ページにスケールできます。
補足:
edge
ランタイム は現在 ISR と互換性がありませんが、cache-control
ヘッダーを手動で設定することでstale-while-revalidate
を活用できます。
ISR を使用するには、getStaticProps
に revalidate
プロパティを追加します:
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// この関数はビルド時にサーバーサイドで呼び出されます。
// 再検証が有効で新しいリクエストがある場合、
// サーバーレス関数で再度呼び出される可能性があります
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
// Next.js はページの再生成を試みます:
// - リクエストがあったとき
// - 最大10秒に1回
revalidate: 10, // 秒単位
}
}
// この関数はビルド時にサーバーサイドで呼び出されます。
// パスが生成されていない場合、サーバーレス関数で
// 再度呼び出される可能性があります
export async function getStaticPaths() {
const res = await fetch('https://.../posts')
const posts = await res.json()
// 投稿に基づいてプリレンダリングするパスを取得
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// ビルド時にこれらのパスのみをプリレンダリングします。
// { fallback: 'blocking' } は、パスが存在しない場合、
// オンデマンドでページをサーバーレンダリングします
return { paths, fallback: 'blocking' }
}
export default Blog
ビルド時にプリレンダリングされたページにリクエストがあると、最初はキャッシュされたページが表示されます。
- 初期リクエスト後10秒以内のすべてのリクエストもキャッシュされ、即時に表示されます。
- 10秒のウィンドウ後、次のリクエストでは依然としてキャッシュされた(古い)ページが表示されます
- Next.js はバックグラウンドでページの再生成をトリガーします。
- ページの生成が成功すると、Next.js はキャッシュを無効化し、更新されたページを表示します。バックグラウンドでの再生成が失敗した場合、古いページは変更されません。
生成されていないパスへのリクエストがあると、Next.js は最初のリクエストでページをサーバーレンダリングします。以降のリクエストではキャッシュから静的ファイルが提供されます。Vercel 上の ISR はキャッシュをグローバルに永続化し、ロールバックを処理します。
補足: 上流のデータプロバイダーがデフォルトでキャッシュを有効にしているか確認してください。
useCdn: false
のように無効にする必要があるかもしれません。そうしないと、再検証で新しいデータを取得して ISR キャッシュを更新できません。Cache-Control
ヘッダーが返されると、CDN でキャッシュが発生する可能性があります(リクエストされているエンドポイントに対して)。
オンデマンド再検証
revalidate
時間を 60
に設定すると、すべての訪問者は1分間同じ生成バージョンのサイトを見ます。キャッシュを無効にする唯一の方法は、1分経過後に誰かがそのページを訪問することです。
v12.2.0
から、Next.js はオンデマンドインクリメンタル静的再生成をサポートし、特定のページの Next.js キャッシュを手動でパージできます。これにより、以下の場合にサイトを更新しやすくなります:
- ヘッドレス CMS からのコンテンツが作成または更新されたとき
- 電子商取引のメタデータが変更されたとき(価格、説明、カテゴリ、レビューなど)
getStaticProps
内で、オンデマンド再検証を使用するために revalidate
を指定する必要はありません。revalidate
が省略されている場合、Next.js はデフォルト値 false
(再検証なし)を使用し、revalidate()
が呼び出されたときにのみオンデマンドでページを再検証します。
補足: ミドルウェア はオンデマンド ISR リクエストに対して実行されません。代わりに、再検証したい正確なパスで
revalidate()
を呼び出してください。たとえば、pages/blog/[slug].js
があり、/post-1
から/blog/post-1
へのリライトがある場合、res.revalidate('/blog/post-1')
を呼び出す必要があります。
オンデマンド再検証の使用
まず、Next.js アプリのみが知る秘密トークンを作成します。この秘密は、再検証 API ルートへの不正アクセスを防ぐために使用されます。以下の URL 構造でルートにアクセスできます(手動またはウェブフック経由):
https://<your-site.com>/api/revalidate?secret=<token>
次に、秘密をアプリケーションの環境変数として追加します。最後に、再検証 API ルートを作成します:
export default async function handler(req, res) {
// 秘密をチェックして有効なリクエストか確認
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: '無効なトークン' })
}
try {
// これはリライトされたパスではなく実際のパスである必要があります
// 例: "/blog/[slug]" の場合は "/blog/post-1"
await res.revalidate('/path-to-revalidate')
return res.json({ revalidated: true })
} catch (err) {
// エラーが発生した場合、Next.js は最後に正常に生成された
// ページを表示し続けます
return res.status(500).send('再検証エラー')
}
}
デモを確認してオンデマンド再検証の動作を確認し、フィードバックを提供してください。
開発中のオンデマンド ISR テスト
next dev
でローカル実行する場合、getStaticProps
はすべてのリクエストで呼び出されます。オンデマンド ISR 設定が正しいことを確認するには、プロダクションビルド を作成し、プロダクションサーバー を起動する必要があります:
$ next build
$ next start
その後、静的ページが正常に再検証されたことを確認できます。
エラー処理と再検証
バックグラウンド再生成中に getStaticProps
内でエラーが発生した場合、または手動でエラーをスローした場合、最後に正常に生成されたページが表示され続けます。次のリクエストで、Next.js は getStaticProps
の呼び出しを再試行します。
export async function getStaticProps() {
// このリクエストでキャッチされないエラーが発生した場合、Next.js は
// 現在表示されているページを無効化せず、
// 次のリクエストで getStaticProps を再試行します
const res = await fetch('https://.../posts')
const posts = await res.json()
if (!res.ok) {
// サーバーエラーがある場合、キャッシュが更新されないように
// 返す代わりにエラーをスローすることを検討してください
// 次に成功するリクエストまで
throw new Error(`投稿の取得に失敗しました、ステータス ${res.status}`)
}
// リクエストが成功した場合、投稿を返し
// 10秒ごとに再検証します
return {
props: {
posts,
},
revalidate: 10,
}
}
セルフホスティング ISR
インクリメンタル静的再生成 (ISR) は、next start
を使用する場合、セルフホスティング Next.js サイト でそのまま動作します。
Kubernetes や HashiCorp Nomad などのコンテナオーケストレーターにデプロイする場合、このアプローチを使用できます。デフォルトでは、生成されたアセットは各ポッドのメモリ内に保存されます。つまり、各ポッドには静的ファイルの独自のコピーがあります。特定のポッドがリクエストを受けるまで、古いデータが表示される可能性があります。
すべてのポッド間で一貫性を確保するには、メモリ内キャッシュを無効にできます。これにより、Next.js サーバーはファイルシステム内の ISR によって生成されたアセットのみを利用するようになります。
Kubernetes ポッド(または類似の設定)で共有ネットワークマウントを使用すると、異なるコンテナ間で同じファイルシステムキャッシュを再利用できます。同じマウントを共有することで、next/image
キャッシュを含む .next
フォルダも共有され、再利用されます。
メモリ内キャッシュを無効にするには、next.config.js
ファイルで isrMemoryCacheSize
を 0
に設定します:
module.exports = {
experimental: {
// デフォルトは50MB
isrMemoryCacheSize: 0, // バイト単位のキャッシュサイズ
},
}
補足: 共有マウントの設定方法によっては、複数のポッドが同時にキャッシュを更新しようとする競合状態を考慮する必要があるかもしれません。
バージョン履歴
バージョン | 変更点 |
---|---|
v12.2.0 | オンデマンド ISR が安定版に |
v12.1.0 | オンデマンド ISR が追加(ベータ)。 |
v12.0.0 | ボット対応 ISR フォールバック が追加。 |
v9.5.0 | ベースパスが追加。 |