Next.js×microCMS|ブログ記事一覧ページをSSG+CSR(ユーザーアクション)の時のページ生成のまとめ

一覧ページをSSG化後、ISR化やCSRでのデータ取得のメリット/デメリットがよく分からなくなったので、疑問に感じたことと併せてまとめてみた。

4/18/2025

🔁 状況整理(/blogs 一覧ページ)

Next.js×microCMSでブログ一覧ページを作成中。
アクセス時はSSGで、スクロールでデータを取得する時はCSRと分けている。

1. /blogs にアクセス

ブログページにアクセスしたら、最初に表示される記事10件はSSG化されており、静的HTMLが返ってくる。

  • getStaticProps が実行され、リミット指定をした10件分のブログ記事が事前にビルドされる。(SSG)
  • revalidate: 30 により、30秒経過すると次のアクセス時にバックグラウンドで最新化される。(ISR)

2. スクロールで記事の追加読み込み(無限スクロール)

初期記事10件以降の記事は、ユーザーのスクロールで新たに取得する。

  • useEffectIntersectionObserver により、ユーザーのスクロールアクションで /api/blogs?offset=10 にリクエストが飛ぶ。

⚡️疑問に思ったこと

❓疑問1|/blogsページはSSG化されているのになぜユーザーアクションで表示できる?
ユーザーアクションのスクロールでさらに10記事取得してるけれど、/blogsページはSSG化されているので、30秒経過しないと一覧は更新されないのでは?

💡無限スクロールでの追加取得はクライアントで描画されているので表示できる
無限スクロールで追加取得は、クライアント側でAPIを経由してリアルタイムに取得している(CSR)。そのため、ページのSSG化関係なく記事は取得され、表示される。


❓疑問2|ユーザー側でデータ取得(一覧更新)ができるならISRは必要ないのでは?
ユーザー側でAPI経由で記事一覧を更新できるなら、SSG化した/blogsページをrevalidateで30秒ごとに最新化(ISR)は必要ないのではないだろうか。二重で更新しているように感じる。

💡ISRは/blogsページ初回表示の鮮度を保つために必要!
revalidate は /blogsページ初回表示のために必要。
無限スクロールで2ページ目以降の記事を取得しても、初回の10件はSSG化された静的HTMLなので、次にアクセスしてもキャッシュからページを返す。記事が増えたとしても/blogsのアクセス時のページは古いまま。

revalidate をつけておけば、静的にビルドされた初期データも定期的に更新される。

補足:ISRの更新タイミング
ISR は次のアクセス時にバックグラウンドで再生成される。指定秒経ったら即座に新しくされるわけではない。

  • アクセスがない場合は再生成されない。
  • 指定秒経過後の最初のアクセス時:再生成が始まる(古いまま表示される可能性あり)
  • 次のアクセス時:再生成されたページが表示される

❓疑問3|revalidateをつけていても、revalidate設定時間より短い間に、1回目アクセスと2回目アクセスの間に記事が増えたら?

💡APIの結果の整合性(順番ズレ問題)は更新頻度次第

【ブログ更新頻度:低〜中(月数回〜週数回)】
通常ユーザーは一覧ページを見たら、他の記事に飛んで、読んだ後に戻ってきたりする。その間にタイミングよく新記事が投稿されて、「初期で取得した10件の記事」と「スクロールで取得した記事」との間で日付のずれが生じて困るといったことはあまり起こることではない。
revalidate: 30 ~ 300秒でも設定可。

【 ブログ更新頻度:高い(1日複数回、あるいは自動投稿等)】
新着記事が頻繁に追加されるようなサイトでは、初期表示の10件(SSG)とその後にCSRで取得するデータの順序がずれてしまい、ユーザーに違和感を与える可能性がある。

📝このようなケースでは、次のような対策が有効:

- 初期10件もCSRで取得し、常に最新に保つ
- revalidate の時間を短くして、できるだけ新鮮な静的ページにする
- サーバーサイドで順番を制御した中間APIなどを用意する(上級者向け)

最適な設計は、「更新頻度」「インフラコスト」「ユーザー体験」のバランスによって決めるのがベスト。

まとめ

SSGは初回アクセスの高速化、CSRは実際の使い心地と分けて考える

SSGは初回アクセスの高速化をしてパフォーマンスを上げてくれる。
そしてCSRは、実装したかった「無限スクロール」というユーザー(私)の使い心地を良くしてくれる。

処理

手段

データの鮮度

初回10件表示

getStaticProps + revalidate

指定秒ごとにバックグラウンドで最新化(ISR)

追加読み込み

API + CSRuseEffect

毎回リアルタイム取得

しかし、SSGは初回表示を高速化をするために静的HTMLを返す。ビルド後にデータ更新されてもデータは古いままキャッシュから返される。
それを改善してくれるのがISRの設定。

そしてCSRは私たちの好みの使い方やデータ取得のタイミングを選べるけれど、API取得にはタイムラグが生じるときがある。

上記二つのメリット/デメリットを、ページの目的によって使い分けていく必要がある。

【 実際の使い方の例 】

🚀 getStaticProps は「とりあえず表示を爆速にしたい」
→ Topページ、記事一覧の最初の10件などに最適

🌀 CSR(useEffect や fetch) は「ユーザー操作に合わせて変化したい」
→ 無限スクロール、検索、絞り込み、ボタンクリックでの表示切替などに◎

項目

getStaticProps(SSG/ISR)

CSR(クライアントサイドレンダリング)

目的

ページの事前生成で高速表示

ユーザー操作に応じた動的な処理・描画

主な役割

初回のHTMLを生成(SEO・速度◎)

無限スクロール・動的フィルター・検索など

タイミング

ビルド時 or revalidateで再生成

ページ表示後のブラウザ内で実行

データの鮮度

再生成タイミング次第

基本リアルタイム(API呼び出し時)

その他Next.jsのレンダリング方法は下記記事を参照。

Next.js のレンダリング方式まとめ|SSG・ISR・SSR・CSR の違いと使い分け