Menu

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

インクリメンタル静的再生成(ISR)には以下のような利点があります:

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

以下は最小限の例です:

app/blog/[id]/page.tsx
TypeScript
interface Post {
  id: string
  title: string
  content: string
}
 
// Next.jsは60秒に1回まで、リクエストが来た際に
// キャッシュを無効化します。
export const revalidate = 60
 
// ビルド時には`generateStaticParams`からのパラメータのみを事前レンダリングします。
// 生成されていないパスへのリクエストが来た場合、
// Next.jsはオンデマンドでページをサーバーレンダリングします。
export const dynamicParams = true // または未知のパスに対して404を返す場合はfalse
 
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>
  )
}

この例の動作は以下の通りです:

  1. next buildの間に、既知のすべてのブログ記事が生成されます(この例では25記事)
  2. これらのページへのすべてのリクエスト(例:/blog/1)はキャッシュされ、即座に返されます
  3. 60秒経過後も、次のリクエストはキャッシュされた(古い)ページを表示します
  4. キャッシュが無効化され、新しいバージョンのページがバックグラウンドで生成を開始します
  5. 正常に生成されると、Next.jsは更新されたページを表示してキャッシュします
  6. /blog/26がリクエストされた場合、Next.jsはこのページをオンデマンドで生成してキャッシュします

リファレンス

ルートセグメントの設定

関数

時間ベースの再検証

これは/blogにブログ記事のリストを取得して表示します。1時間後、このページのキャッシュは次のページ訪問時に無効化されます。その後、バックグラウンドで最新のブログ記事を含む新しいバージョンのページが生成されます。

app/blog/page.tsx
TypeScript
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>
  )
}

より長い再検証時間を設定することをお勧めします。例えば、1秒ではなく1時間です。より正確な制御が必要な場合は、オンデマンド再検証の使用を検討してください。リアルタイムデータが必要な場合は、動的レンダリングに切り替えることを検討してください。

revalidatePathを使用したオンデマンド再検証

より正確な再検証方法として、revalidatePath関数を使用してページをオンデマンドで無効化できます。

例えば、この Server Action は新しい投稿を追加した後に呼び出されます。Server Componentでデータをどのように取得するかにかかわらず、fetchを使用するか、データベースに接続するかにかかわらず、これはルート全体のキャッシュをクリアし、Server Componentが新しいデータを取得できるようにします。

app/actions.ts
'use server'
 
import { revalidatePath } from 'next/cache'
 
export async function createPost() {
  // キャッシュ内の/postsルートを無効化
  revalidatePath('/posts')
}
app/actions.js
'use server'
 
import { revalidatePath } from 'next/cache'
 
export async function createPost() {
  // キャッシュ内の/postsルートを無効化
  revalidatePath('/posts')
}

デモを見るソースコードを確認する

revalidateTagを使用したオンデマンド再検証

ほとんどのユースケースでは、パス全体を再検証することをお勧めします。より細かい制御が必要な場合は、revalidateTag関数を使用できます。例えば、個々のfetch呼び出しにタグを付けることができます:

app/blog/page.tsx
TypeScript
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を使用できます:

app/blog/page.tsx
TypeScript
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()
  // ...
}

その後、Server ActionsまたはRoute HandlerrevalidateTagを使用できます:

app/actions.ts
'use server'
 
import { revalidateTag } from 'next/cache'
 
export async function createPost() {
  // キャッシュ内の'posts'タグが付いたすべてのデータを無効化
  revalidateTag('posts')
}
app/actions.js
'use server'
 
import { revalidateTag } from 'next/cache'
 
export async function createPost() {
  // キャッシュ内の'posts'タグが付いたすべてのデータを無効化
  revalidateTag('posts')
}

キャッチされない例外の処理

データの再検証を試みている間にエラーが発生した場合、最後に正常に生成されたデータがキャッシュから引き続き提供されます。次の後続のリクエストで、Next.jsはデータの再検証を再試行します。エラー処理の詳細については、こちらをご覧ください

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

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

セルフホスティングの場合、ISRキャッシュはNext.jsサーバーのファイルシステム(ディスク)に保存されます。これはPagesと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ランタイム(デフォルト)を使用する場合のみサポートされています。
  • Static Exportを作成する場合、ISRはサポートされていません。
  • 静的にレンダリングされたルート内に複数のfetchリクエストがあり、それぞれが異なるrevalidate頻度を持っている場合、ISRには最も短い時間が使用されます。ただし、それらの再検証頻度はデータキャッシュによって尊重されます。
  • ルートで使用されるfetchリクエストのいずれかがrevalidate時間が0または明示的なno-storeを持っている場合、そのルートは動的にレンダリングされます。
  • オンデマンド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が導入