コンテンツにスキップ

インクリメンタル静的再生成(ISR)の実装方法

インクリメンタル静的再生成(ISR)により、サイト全体を再構築することなく静的コンテンツを更新できます。

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

最小限の例を以下に示します

app/blog/[id]/page.tsx
interface Post {
  id: string
  title: string
  content: string
}
 
// Next.js will invalidate the cache when a
// request comes in, at most once every 60 seconds.
export const revalidate = 60
 
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: Promise<{ id: string }>
}) {
  const { id } = await params
  const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then(
    (res) => res.json()
  )
  return (
    <main>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </main>
  )
}

この例がどのように機能するか

  1. next build 中に、すべての既知のブログ投稿が生成されます
  2. これらのページへのすべてリクエスト(例:/blog/1)はキャッシュされ、瞬時に応答します
  3. 60秒経過後も、次回の要求はキャッシュされた(古い)ページを返します
  4. キャッシュが無効化され、ページの新しいバージョンがバックグラウンドで生成され始めます
  5. 正常に生成されると、次回の要求で更新されたページが返され、それ以降のリクエストのためにキャッシュされます
  6. /blog/26 がリクエストされ、それが存在する場合、ページはオンデマンドで生成されます。この動作は、異なる dynamicParams 値を使用することで変更できます。ただし、投稿が存在しない場合は 404 が返されます。

リファレンス

ルートセグメント設定

関数

時間ベースの再検証

これは /blog でブログ投稿のリストを取得して表示します。1 時間経過すると、次の訪問者もキャッシュされた(古い)バージョンのページをすぐに受け取り、高速な応答が得られます。同時に、Next.js はバックグラウンドで新しいバージョンの生成をトリガーします。新しいバージョンが正常に生成されると、キャッシュされたバージョンが置き換えられ、後続の訪問者は更新されたコンテンツを受け取ります。

app/blog/page.tsx
interface Post {
  id: string
  title: string
  content: string
}
 
export const revalidate = 3600 // invalidate every hour
 
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 を使用するか、データベースに接続するかに関わらず、これはルート全体のキャッシュを無効化します。そのルートへの次回の要求は再生成をトリガーし、新しいデータを配信し、後続のリクエストのためにキャッシュされます。

注意: revalidatePath はキャッシュエントリを無効化しますが、生成は次回の要求時に行われます。次回の要求を待たずにキャッシュエントリをすぐに再生成したい場合は、Pages Router の res.revalidate メソッドを使用できます。App Router で積極的な再生成機能を提供する新しいメソッドの追加に取り組んでいます。

app/actions.ts
'use server'
 
import { revalidatePath } from 'next/cache'
 
export async function createPost() {
  // Invalidate the cache for the /posts route
  revalidatePath('/posts')
}

デモを表示 および ソースコードを探索

revalidateTag によるオンデマンド再検証

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

app/blog/page.tsx
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
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() {
  // Invalidate all data tagged with 'posts'
  revalidateTag('posts')
}

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

データ再検証の試行中にエラーが発生した場合、最後に正常に生成されたデータはキャッシュから提供され続けます。次回の subsequent リクエストで、Next.js はデータの再検証を再試行します。エラー処理についてさらに詳しく

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

キャッシュされたページとデータを永続ストレージに保持したい場合、または 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 ランタイム(デフォルト)を使用している場合にのみサポートされます。
  • 静的エクスポートを作成する際には ISR はサポートされません。静的エクスポート
  • 静的レンダリングされたルートで複数の fetch リクエストがあり、それぞれ異なる revalidate 周波数の場合、最も低い時間が ISR に使用されます。ただし、それらの revalidate 周波数は、データキャッシュによって引き続き尊重されます。
  • ルートで使用されている fetch リクエストのいずれかに revalidate 時間が 0 であるか、明示的な no-store が指定されている場合、ルートは動的にレンダリングされます。
  • プロキシはオンデマンド ISR リクエストでは実行されないため、プロキシ内のパス書き換えやロジックは適用されません。正確なパスを再検証していることを確認してください。たとえば、書き換えられた /post-1 ではなく、/post/1 です。

プラットフォームのサポート

デプロイメントオプションサポート
Node.jsサーバーはい
Dockerコンテナはい
静的エクスポートいいえ
アダプタープラットフォーム固有

Next.js をセルフホスティングする際に ISR を構成する方法については、こちらをご覧ください。

バージョン履歴

バージョン変更履歴
v14.1.0カスタム cacheHandler は安定しています。
v13.0.0App Router が導入されました。
v12.2.0Pages Router: オンデマンド ISR は安定しています
v12.0.0Pages Router: ボット対応 ISR フォールバックが追加されました。
v9.5.0Pages Router: 安定版 ISR が導入されました。