Next.jsのISRとは?初心者向けにわかりやすく基本から解説

Webサイトを作っていると、「リアルタイムで更新したいけど、パフォーマンスも大事にしたい…」と悩むことはありませんか。

そんなときに役立つのが、Next.jsの「ISR」という機能です。

この記事では、ISRについて初心者の方にもわかりやすく解説していきます。

では、ISRについて詳しく見ていきましょう。

Next.jsのISRとは?基本的な仕組みと役割を解説

Incremental Static Regeneration(ISR)とは何か

ISRは「Incremental Static Regeneration」の略で、日本語では「段階的な静的ページの再生成」といった意味です。

簡単に言うと、「最初は静的なページを表示して、必要なときだけページを更新する」という仕組みです。

たとえば、ブログサイトを運営しているとします。記事は1日に1回程度しか更新されないけど、コメント欄は頻繁に更新されるような場合、ISRを使うとページの管理が楽になります。

ISRがNext.jsで実現するもの

ISRを使うと、Next.jsは以下のような動作を実現します。

  • 最初のアクセス時に静的なページを素早く表示
  • 設定した時間が経過したら、バックグラウンドで新しいページを生成
  • 次のアクセス時には、新しく生成されたページを表示

このような動作により、ユーザーはいつも高速なページ表示を体験できます。

ISRを活用するメリットとデメリット

ISRを使うと、以下のようなメリットがあります。

  • ページの表示が高速
  • サーバーへの負荷が少ない
  • コンテンツの更新も可能

一方で、以下のようなデメリットもあります。

  • 更新のタイミングを細かく制御できない
  • 古いコンテンツが表示される可能性がある
  • 設定や理解が少し複雑

これらのメリット・デメリットを理解した上で、プロジェクトに合わせて使用するかどうかを決めましょう。

ISRとSSG・SSR・CSRとの違いを理解する

SSG(Static Site Generation)の特徴と比較

SSGは「ビルド時に全てのページを生成する」方式です。ブログの記事など、更新が少ないコンテンツに向いています。

一方、ISRは「必要なタイミングでページを再生成する」方式なので、更新頻度が中程度のコンテンツに適しています。

SSR(Server-Side Rendering)との処理フローの違い

SSRは「アクセスがあるたびにサーバーでページを生成する」方式です。常に最新の情報を表示できますが、サーバーへの負荷が高くなります。

ISRはその中間で、「一定期間はキャッシュを使い、必要なときだけ再生成する」という方式を取ります。

CSR(Client-Side Rendering)とISRの使い分け

CSRは「JavaScriptを使ってブラウザ側でページを生成する」方式です。

ユーザーの操作に応じてページの内容を動的に変更できる一方で、初回表示に時間がかかるという特徴があります。

ISRはCSRと違って、最初から完成したページを表示できます。そのため、検索エンジンにも認識されやすく、SEO対策にも効果的です。

On-Demand ISRの仕組みと使用方法

On-Demand ISRとは?動的なページ再生成の動作原理

On-Demand ISRは、「必要なときに手動でページを再生成する」機能です。

たとえば、CMSでブログ記事を更新したときに、該当するページだけを再生成することができます。

VS Codeでページを作成する場合、以下のようなコードを書きます。

export default function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </div>
  )
}

export async function getStaticProps({ params }) {
  const post = await getBlogPost(params.id)

  return {
    props: { post },
    revalidate: 60 // 60秒ごとに再検証
  }
}

revalidatePathやAPIリクエストを使ったオンデマンドリクエスト

On-Demand ISRを使うには、APIルートを作成する必要があります。

以下は、記事を更新したときにページを再生成するAPIの例です。

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  try {
    await res.revalidate('/blog/[id]')
    return res.json({ revalidated: true })
  } catch (err) {
    return res.status(500).send('Error revalidating')
  }
}

VercelでのデプロイとOn-Demand ISRの活用事例

Vercelにデプロイすると、On-Demand ISRが自動的に利用可能になります。

CMSと連携する場合、以下のような流れでページを更新できます。

1. コンテンツの更新

CMSで記事を更新する

2. 更新通知

Webhookで更新を通知する

3. API呼び出し

APIルートが呼び出される

4. ページ再生成

該当ページが再生成される

この仕組みにより、必要なページだけを効率的に更新できます。

Next.js ISRの利用に必要なコードと主要メソッド

revalidateとgenerateStaticParamsの基本的な使い方

ISRを使うには、getStaticProps関数でrevalidateオプションを設定します。

以下は、商品一覧ページの例です。

export async function getStaticProps() {
  const products = await getProducts()

  return {
    props: {
      products
    },
    revalidate: 3600 // 1時間ごとに再検証
  }
}

dynamicParamsと動的ページの組み合わせ

動的なURLを持つページでISRを使う場合、getStaticPathsdynamicParamsオプションを組み合わせます。

URLのパラメータに応じて異なるコンテンツを表示する場合、以下のようなコードを書きます。

export async function getStaticPaths() {
  const products = await getProducts()
  const paths = products.map(product => ({
    params: { id: product.id.toString() }
  }))

  return {
    paths,
    fallback: 'blocking'
  }
}

export async function getStaticProps({ params }) {
  const product = await getProduct(params.id)

  return {
    props: { product },
    revalidate: 60,
    notFound: !product
  }
}

dynamicオプション設定とキャッシュの制御方法

キャッシュの動作を細かく制御したい場合、next.config.jsでオプションを設定できます。

以下は、特定のページだけキャッシュの設定を変更する例です。

module.exports = {
  async headers() {
    return [
      {
        source: '/products/:id',
        headers: [
          {
            key: 'Cache-Control',
            value: 's-maxage=60, stale-while-revalidate=30'
          }
        ]
      }
    ]
  }
}

ISRとデータフェッチ:パフォーマンス最適化のベストプラクティス

フェッチとキャッシュの関係を理解する

ISRでデータをフェッチする場合、以下のような流れで処理が行われます。

  1. ユーザーがページにアクセス
  2. キャッシュされたページがあれば表示
  3. バックグラウンドでデータを再取得
  4. 新しいページを生成してキャッシュを更新

このサイクルを理解することで、効率的なデータ取得の設計が可能になります。

データリクエスト(requests)と再生成タイミングの設定

データの更新頻度に応じて、再生成のタイミングを調整できます。

たとえば、商品の在庫状況を表示するページでは、以下のように設定します。

export async function getStaticProps() {
  const inventory = await getInventory()
  const lastUpdate = new Date().toISOString()

  return {
    props: {
      inventory,
      lastUpdate
    },
    revalidate: 300 // 5分ごとに再検証
  }
}

実装で考慮すべきタイミングと挙動検証のポイント

ISRの実装時は、以下のポイントに気をつけましょう。

  • データの更新頻度
  • サーバーの負荷
  • ユーザーの体験

これらのバランスを考えながら、適切な再生成間隔を設定することが大切です。

ISR対応ページ作成の実装ガイド

生成と再生成のプロセスを理解する

ISR対応ページを作成するには、まずプロジェクトの初期設定が必要です。

VS Codeで新しいNext.jsプロジェクトを作成し、以下のような基本的な構造を作ります。

// pages/products/[id].js
import { useState } from 'react'

export default function Product({ product, lastUpdate }) {
  const [isLoading, setIsLoading] = useState(false)

  return (
    <div>
      <h1>{product.name}</h1>
      <p>価格: {product.price}</p>
      <p>最終更新: {lastUpdate}</p>
    </div>
  )
}

export async function getStaticPaths() {
  const products = await getProducts()

  return {
    paths: products.map(product => ({
      params: { id: product.id.toString() }
    })),
    fallback: true
  }
}

再生成に必要な情報の指定方法

ページの再生成に必要な情報は、getStaticProps関数で指定します。

データベースやAPIからデータを取得し、ページのプロパティとして渡す処理を書きます。

export async function getStaticProps({ params }) {
  try {
    const product = await getProduct(params.id)
    const lastUpdate = new Date().toISOString()

    return {
      props: {
        product,
        lastUpdate
      },
      revalidate: 60,
      notFound: !product
    }
  } catch (error) {
    return {
      notFound: true
    }
  }
}

ISR対応ページのデプロイ手順

ISR対応ページをデプロイする際は、以下の手順で進めます。

  • プロジェクトをビルドする
  • 環境変数を設定する
  • デプロイを実行する
  • 動作確認を行う

Vercelを使う場合、GitHubと連携するだけで自動的にデプロイされます。

ISRのキャッシュとステールデータ(stale data)の管理

キャッシュ制御戦略:生成時間と更新タイミング

キャッシュを効率的に管理するために、データの特性に応じた戦略を立てます。

以下のようなコードで、キャッシュの有効期限を設定できます。

export async function getStaticProps() {
  const data = await fetchData()

  return {
    props: { data },
    revalidate: process.env.NODE_ENV === 'production'
      ? 3600  // 本番環境では1時間
      : 10    // 開発環境では10秒
  }
}

stale-while-revalidateパターンの動作詳細

stale-while-revalidateパターンは、古いデータを表示しながら新しいデータを取得する方式です。

このパターンをNext.jsで実装する場合、以下のようなコードを書きます。

export async function getStaticProps() {
  const cacheKey = 'blog-posts'
  const cachedData = await redis.get(cacheKey)

  if (cachedData) {
    return {
      props: { posts: JSON.parse(cachedData) },
      revalidate: 30
    }
  }

  const posts = await fetchPosts()
  await redis.set(cacheKey, JSON.stringify(posts))

  return {
    props: { posts },
    revalidate: 30
  }
}

Vercelキャッシュの検証・管理方法

Vercelでは、開発者ダッシュボードからキャッシュの状態を確認できます。

以下のような処理を追加することで、キャッシュの動作を検証できます。

export default function Page({ data, timestamp }) {
  return (
    <div>
      <h1>データ表示</h1>
      <p>生成時刻: {timestamp}</p>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  )
}

export async function getStaticProps() {
  const timestamp = new Date().toISOString()
  const data = await fetchData()

  console.log(`ページ生成: ${timestamp}`)

  return {
    props: {
      data,
      timestamp
    },
    revalidate: 60
  }
}

ISRを利用した動的コンテンツ生成の実例

ブログ記事や投稿データ(posts)の再生成

ブログサイトでISRを活用する例を見てみましょう。

記事の一覧ページと詳細ページを作る場合、以下のようなコードになります。

// pages/blog/index.js
export default function BlogList({ posts }) {
  return (
    <div>
      <h1>ブログ記事一覧</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </div>
  )
}

export async function getStaticProps() {
  const posts = await getPosts()

  return {
    props: { posts },
    revalidate: 60
  }
}

複数ページの効率的なキャッシュ管理

多数のページを扱う場合、キャッシュを効率的に管理する必要があります。

以下のコードは、カテゴリー別の記事一覧を管理する例です。

// pages/category/[slug].js
export default function CategoryPage({ posts, category }) {
  return (
    <div>
      <h1>{category.name}の記事一覧</h1>
      <PostList posts={posts} />
    </div>
  )
}

export async function getStaticPaths() {
  const categories = await getCategories()

  return {
    paths: categories.map(category => ({
      params: { slug: category.slug }
    })),
    fallback: 'blocking'
  }
}

export async function getStaticProps({ params }) {
  const category = await getCategory(params.slug)
  const posts = await getPostsByCategory(params.slug)

  return {
    props: {
      category,
      posts
    },
    revalidate: 3600
  }
}

ユーザーアクセスに基づくレンダリングの具体例

ユーザーの行動に応じてページを再生成する例を見てみましょう。

以下は、アクセス数の多いページを優先的に更新する仕組みです。

export async function getStaticProps({ params }) {
  const pageId = params.id
  const accessCount = await getAccessCount(pageId)

  // アクセス数に応じて再生成間隔を調整
  const revalidateTime = accessCount > 1000 ? 300 : 3600

  const data = await fetchPageData(pageId)

  return {
    props: {
      data,
      lastUpdate: new Date().toISOString()
    },
    revalidate: revalidateTime
  }
}

ISRの課題と解決策:効率的なページ再生性の最適化

ISRで頻発するエラーとその対応策

ISRを使う際によく発生するエラーには、以下のようなものがあります。

  • データ取得の失敗
  • 再生成のタイミングのずれ
  • キャッシュの整合性の問題

これらのエラーに対処するため、エラーハンドリングを実装します。

export async function getStaticProps() {
  try {
    const data = await fetchData()

    if (!data) {
      return {
        notFound: true
      }
    }

    return {
      props: { data },
      revalidate: 60
    }
  } catch (error) {
    console.error('データ取得エラー:', error)

    return {
      props: { error: 'データの取得に失敗しました' },
      revalidate: 30
    }
  }
}

再生成における真偽値(true/false)の設定例

再生性のタイミングを制御するために、状況に応じて設定を切り替える方法を見てみましょう。

以下は環境変数を使って再生成の有無を制御する例です。

export async function getStaticProps() {
  const enableRevalidate = process.env.ENABLE_REVALIDATE === 'true'
  const data = await fetchData()

  return {
    props: { data },
    revalidate: enableRevalidate ? 60 : false
  }
}

ISRで利用するパスやヘッダ設定の注意点

ISRを使う際は、パスやヘッダの設定にも気をつける必要があります。

以下は、適切なキャッシュヘッダを設定する例です。

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/blog/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, s-maxage=60, stale-while-revalidate=30'
          }
        ]
      }
    ]
  }
}

まとめ:ISRの活用ポイント

ここまでISRについて詳しく見てきました。最後に、ISRを使う際のポイントをまとめます。

  • データの更新頻度に応じて再生成間隔を設定する
  • エラーハンドリングを適切に実装する
  • キャッシュの設定に気を配る
  • パフォーマンスとユーザー体験のバランスを取る

ISRは難しそうに見えますが、基本的な使い方を理解すれば、便利な機能として活用できます。

Next.jsをマスターしよう

この記事で学んだNext.jsの知識をさらに伸ばしませんか?
Udemyには、現場ですぐ使えるスキルを身につけられる実践的な講座が揃っています。