1. はじめに
このドキュメントでは、Next.jsのレンダリング方式の一つであるISR(Incremental Static Regeneration)について解説します。 その後、外部APIの利用回数制限がある場合に、どのようにISRが有効かを天気予報サイトを例に説明します。
本プロジェクトの概要
サイネージ向けのお天気サイトです。全国、地区(北海道〜九州)、都道府県、市町村と階層的に天気情報を表示します。 天気データはOpenWeatherMap APIから取得しています。
2. Webページのレンダリング方式
まず、Webページがどのタイミングで生成されるかによって、大きく4つの方式があります。
SSG Static Site Generation
ビルド時にHTMLを生成
- 最速の表示
- 更新にはビルドが必要
- 企業サイト等に最適
SSR Server-Side Rendering
リクエスト毎にHTMLを生成
- 常に最新データ
- サーバー負荷が高い
- 外部API呼び出し多
ISR Incremental Static Regeneration
静的生成+定期更新
- 高速表示を維持
- 定期的にデータ更新
- API呼び出しを削減
CSR Client-Side Rendering
ブラウザでHTMLを生成
- 初期表示が遅い
- SEOに課題あり
- 管理画面等に最適
レンダリング方式の比較表
| 方式 | 生成タイミング/場所 | 表示速度 | データ鮮度 | API呼び出し |
|---|---|---|---|---|
| SSG | ビルド時 | 最速 | ビルド時点 | ビルド時のみ |
| SSR | リクエスト毎 | 遅め | 常に最新 | リクエスト毎 |
| ISR | 定期的に再生成 | 高速 | 設定間隔で更新 | 再検証時のみ |
| CSR | ブラウザ上 | 初期は遅い | 常に最新 | リクエスト毎 |
3. ISRの仕組み
ISRは「静的サイトのパフォーマンス」と「動的サイトのデータ鮮度」を両立する仕組みです。
ISRの動作フロー
HTMLを即座に返却
データ再取得
更新されたHTML
ポイント:stale-while-revalidate パターン
ユーザーにはまず「古いかもしれないが有効なデータ(stale)」を即座に返し、 裏側で「新しいデータを取得(revalidate)」します。 これにより、ユーザーは待たずに高速表示を体験できます。
4. 本プロジェクトでの活用
4.1 なぜISRを採用したか
本プロジェクトでは、OpenWeatherMap APIを使用して天気データを取得しています。このAPIには以下の制限があります。
| プラン | 制限 | リクエスト数 |
|---|---|---|
| 無料プラン | レートリミット | 60リクエスト/分 |
| One Call API 3.0 | 日次リミット | 1,000リクエスト/日(無料枠) |
もしSSR(リクエスト毎に取得)を採用していた場合、ユーザーがアクセスするたびにAPIを呼び出すため、すぐに制限に達してしまいます。
4.2 レンダリング方式の選択理由
| 方式 | 天気サイトでの問題 | 採用可否 |
|---|---|---|
| SSG | 天気データが更新されない(ビルド時点のまま) | 不適 |
| SSR | API制限にすぐ達する | 不適 |
| CSR | API制限にすぐ達する | 不適 |
| ISR | 定期更新でAPI呼び出しを最小化しつつ鮮度を保つ | 最適 |
5. 実装箇所(コード抜粋)
5.1 OpenWeatherMap APIでの実装
Next.jsでは、fetch関数にnext.revalidateオプションを指定することでISRを実現します。
lib/api-client.ts
/**
* OpenWeatherMap API への共通リクエスト処理
*/
async function fetchFromOpenWeatherMap<T>(
endpoint: string,
params: Record<string, string | number>
): Promise<T> {
const url = new URL(`${OPENWEATHERMAP_BASE_URL}${endpoint}`)
// パラメータ設定...
const response = await fetch(url.toString(), {
next: { revalidate: 1800 }, // 30分間キャッシュ、その後再検証
})
return response.json() as Promise<T>
}
revalidate: 1800 の意味
この設定により、取得したデータは30分間(1800秒)キャッシュされます。 30分経過後、次のリクエスト時にバックグラウンドでデータが再取得されます。
5.2 One Call API 3.0 でも同様に実装
lib/api-client.ts
/**
* One Call API 3.0 への共通リクエスト処理
* 現在の天気、48時間の時間別予報、8日間の日別予報を一度に取得
*/
async function fetchFromOneCallApi<T>(
endpoint: string,
params: Record<string, string | number>
): Promise<T> {
const url = new URL(`${OPENWEATHERMAP_ONECALL_URL}${endpoint}`)
// パラメータ設定...
const response = await fetch(url.toString(), {
next: { revalidate: 1800 }, // 30分間キャッシュ、その後再検証
})
return response.json() as Promise<T>
}
7. まとめ
- API利用制限への対応:定期更新により呼び出し回数を削減
- データの鮮度維持:30分間隔で自動更新
- 上記に伴うコスト削減:API従量課金の抑制
ISRの適用場面に関する補足
ISRは、天気予報サイトのように「リアルタイム性は必要だが秒単位の更新は不要」なコンテンツに最適です。 ニュースサイト、商品情報ページ、ブログ記事なども同様にISRの恩恵を受けられます。