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を使う場合、getStaticPaths
とdynamicParams
オプションを組み合わせます。
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でデータをフェッチする場合、以下のような流れで処理が行われます。
- ユーザーがページにアクセス
- キャッシュされたページがあれば表示
- バックグラウンドでデータを再取得
- 新しいページを生成してキャッシュを更新
このサイクルを理解することで、効率的なデータ取得の設計が可能になります。
データリクエスト(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は難しそうに見えますが、基本的な使い方を理解すれば、便利な機能として活用できます。