Incremental Static Regeneration (ISR)
Incremental Static Regeneration (ISR) を使用すると、以下のことが可能になります:
- サイト全体の再ビルドなしで静的コンテンツを更新する
- ほとんどのリクエストに対してプリレンダリングされた静的ページを提供し、サーバー負荷を軽減する
- ページに適切な
cache-control
ヘッダーが自動的に追加されることを保証する - 大量のコンテンツページを長時間の
next build
なしで処理する
以下は最小限の例です:
- TypeScript
- JavaScript
interface Post {
id: string
title: string
content: string
}
// Next.js は 60秒ごとに、キャッシュを無効にします;
export const revalidate = 60
// ビルド時に `generateStaticParams` からのパラメータのみを事前レンダリングします。
// 生成されていないパスへのリクエストが行われた場合、Next.js はオンデマンドでページをサーバーレンダリングします。
export const dynamicParams = true // または false に設定して、未知のパスで404を返す
export async function generateStaticParams() {
const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({ params }: { params: { id: string } }) {
const post: Post = await fetch(
`https://api.vercel.app/blog/${params.id}`
).then((res) => res.json())
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
// Next.js は 60秒ごとに、キャッシュを無効にします;
export const revalidate = 60
// ビルド時に `generateStaticParams` からのパラメータのみを事前レンダリングします。
// 生成されていないパスへのリクエストが行われた場合、Next.js はオンデマンドでページをサーバーレンダリングします。
export const dynamicParams = true // または false に設定して、未知のパスで404を返す
export async function generateStaticParams() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
return posts.map((post) => ({
id: String(post.id),
}))
}
export default async function Page({ params }) {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
この例がどのように機能するか:
next build
時に、すべての既知のブログ投稿が生成されます(この例では25)- これらのページに対するすべてのリクエスト(例:
/blog/1
)はキャッシュされ、即時に応答します - 60秒が経過した後、次のリクエストはまだキャッシュされた(古い)ページを表示します
- キャッシュが無効化され、新しいバージョンのページがバックグラウンドで生成され始めます
- 正常に生成されたら、Next.js は更新されたページを表示し、キャッシュします
/blog/26
がリクエストされた場合、Next.js はこのページをオンデマンドで生成し、キャッシュします
参照
ルートセグメントの設定
関数
例
時間ベースの再検証
これは /blog
でブログ投稿のリストを取得して表示します。1時間後、このページのキャッシュは次回のページ訪問時に無効になります。その後、バックグラウンドで最新のブログ投稿で新しいバージョンのページが生成されます。
- TypeScript
- JavaScript
interface Post {
id: string
title: string
content: string
}
export const revalidate = 3600 // 1時間ごとに無効化する
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts: Post[] = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
export const revalidate = 3600 // 1時間ごとに無効化する
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<main>
<h1>Blog Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
)
}
高頻度の再検証ではなく、例えば1秒よりも1時間のように高い頻度を設定することをお勧めします。より正確な再検証が必要な場合は、オンデマンド再検証を考慮してください。リアルタイムデータが必要な場合は、動的レンダリング に切り替えてください。
revalidatePath
を使用したオンデマンド再検証
より正確な再検証方法として、revalidatePath
関数を使用してページをオンデマンドで無効にします。
たとえば、新しい投稿を追加した後にこのサーバーアクションを呼び出します。fetch
を使用してデータを取得するか、データベースに接続するかにかかわらず、サーバーコンポーネント内のデータ取得方法に関係なく、これはルート全体のキャッシュをクリアし、サーバーコンポーネントが新しいデータを取得できるようにします。
- TypeScript
- JavaScript
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// キャッシュ内の /posts ルートを無効化する
revalidatePath('/posts')
}
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
// キャッシュ内 の /posts ルートを無効化する
revalidatePath('/posts')
}
デモを見る および ソースコードを探索する
revalidateTag
を使用したオンデマンド再検証
ほとんどのユースケースでは、ルート全体を再検証することをお勧めします。より細かい制御が必要な場合は、revalidateTag
関数を使用できます。たとえば、個々の fetch
呼び出しにタグを付けることができます:
- TypeScript
- JavaScript
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog', {
next: { tags: ['posts'] },
})
const posts = await data.json()
// ...
}
ORM を使用するか、データベースに接続している場合 は、unstable_cache
を使用できます:
- TypeScript
- JavaScript
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
import { unstable_cache } from 'next/cache'
import { db, posts } from '@/lib/db'
const getCachedPosts = unstable_cache(
async () => {
return await db.select().from(posts)
},
['posts'],
{ revalidate: 3600, tags: ['posts'] }
)
export default async function Page() {
const posts = getCachedPosts()
// ...
}
その後、サーバーアクション または ルートハンドラー で revalidateTag
を使用できます:
- TypeScript
- JavaScript
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// キャッシュ内の 'posts' タグが付けられたすべてのデータを無効化する
revalidateTag('posts')
}
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
// キャッシュ内の 'posts' タグが付けられたすべてのデータを無効化する
revalidateTag('posts')
}
捕捉されない例外の処理
データを再検証しようとしたときにエラーが発生すると、最後に正常に生成されたデータがキャッシュから提供され続けます。次のリクエストで、Next.js はデータの再検証を再試行します。エラーハンドリングについて詳しくはこちら。
キャッシュ場所のカスタマイズ
キャッシュおよびページの再検証(Incremental Static Regeneration を使用)は、同じ共有キャッシュを使用します。Vercel にデプロイしている場合、ISR キャッシュは自動的に耐久性のあるストレージに保存されます。
セルフホスティングしている場合、ISR キャッシュは Next.js サーバー上のファイルシステム(ディスク)に保存されます。Pages と App Router の両方を使用してセルフホストする場合に自動的に機能します。
Next.js アプリケーションの複数のコンテナまたはインスタンス間でキャッシュされたページとデータを保存したり、キャッシュを共有したい場合は、Next.js キャッシュの場所を設定できます。詳細はこちらをご覧ください。
トラブルシューティング
ローカル開発でキャッシュされたデータのデバッグ
fetch
API を使用している場合、リクエストがキャッシュされているか、またはキャッシュされていないかを理解するために追加のログ記録を追加できます。logging
オプションについて詳しくはこちら。
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
正しいプロダクションの動作の確認
プロダクションでページが正しくキャッシュされ、再検証されていることを確認するには、next build
を実行した後に next start
を実行して、プロダクションNext.jsサーバーを実行し、ローカルでテストできます。
これにより、プロダクション環境でのISRの動作をテストできます。さらにデバッグするには、次の環境変数を .env
ファイルに追加します:
NEXT_PRIVATE_DEBUG_CACHE=1
これにより、Next.jsサーバーコンソールにISRキャッシュヒットとミスが記録されます。出力を調査して next build
中に生成されたページと、パスがオンデマンドでアクセスされる際にページがどのように更新されるかを確認することができます。
注意事項
- ISRはNode.jsランタイムを使用している場合のみにサポートされています(デフォルト)。
- 静的エクスポート を作成する場合はISRがサポートされません。
- 静的にレンダリングされたルートに複数の
fetch
リクエストがあり、それぞれが異なるrevalidate
周期を持つ場合は、ISRには最短の時間が使用されます。ただし、これらの再検証頻度は データキャッシュ によって引き続き尊重されます。 - ルートで使用される
fetch
リクエストのいずれかがrevalidate
時間0
または明示的なno-store
を持っている場合、そのルートは 動的にレンダリングされます。 - オンデマンド ISR リクエストにはミドルウェアは実行されません。このため、ミドルウェアでのパスの書き換えやロジックは適用 されません。正確なパスを再検証していることを確認してください。たとえば、書き換えられた
/post-1
ではなく、/post/1
のように。
バージョン履歴
Version | Changes |
---|---|
v14.1.0 | カスタム cacheHandler が安定しました |
v13.0.0 | app router が導入されました |
v12.2.0 | pages router: オンデマンドISRが安定しました |
v12.0.0 | pages router: Bot対応ISRフォールバック が追加されました |
v9.5.0 | pages router: 安定版ISRが導入されました |