CSR/SSR/SSG/ISRの違いと選び方:Next.js・Astroで迷わない判断基準
レンダリング方式4種の仕組み・速度・SEO・運用を、どの場面でどれを選ぶか中心に整理。Next.jsとAstroの実装と検証コマンド付き。
「このページ、もっと速くして」
そうClaude Codeに頼んだら、会員ダッシュボードまで静的化されて、別の人の残高が一瞬チラ見えする状態になっていました。本番に出る前に気づいたから笑い話で済みましたが、背筋は冷えました。
速くなったのは事実です。でも、速さと引き換えに「鮮度」と「個人化」を捨てていた。これがレンダリング方式選びの怖いところで、どれが正解かはページごとに違うんです。記事ページの正解と、カートページの正解は真逆だったりします。
CSR、SSR、SSG、ISR。名前は似ていて、説明文を読んでも頭に入りにくい。でも仕組みは拍子抜けするほど単純です。「HTMLをいつ、どこで作るか」が違うだけ。今日はそこを起点に、自分のページがどれに当てはまるかを迷わず選べるようにします。
この記事の要点
- 4つの違いは「HTMLをいつ・どこで作るか」だけ。CSR=ブラウザで、SSR=リクエストのたびにサーバーで、SSG=ビルド時に一度、ISR=ビルド時+あとから更新。
- SEOで強いのはSSG/SSG寄りのISR。CSRは初回が空のHTMLになりがちで、検索とAI回答エンジンに弱い。
- 「鮮度・個人化・更新頻度・コスト」の4軸で決める。速さだけで選ぶと事故る。
- 記事・LP=SSG、商品一覧・ニュース=ISR、ダッシュボード・検索・カート=SSR。これが基本形。
- Next.jsは
export const revalidateやリクエスト時APIで方式が切り替わる。AstroはデフォSSGで、prerender = falseを付けた所だけSSR。
まず「HTMLをいつ作るか」だけで整理する
難しい言葉を一回忘れて、料理にたとえます。
- **CSR(クライアントサイドレンダリング)**は、お客さんのテーブルで料理を組み立てる方式。最初は空の皿(中身のないHTML)だけ届いて、ブラウザがJavaScriptを動かして具を盛り付けます。
- **SSR(サーバーサイドレンダリング)**は、注文が入るたびに厨房で作る方式。リクエストのたびにサーバーがHTMLを作って返します。
- **SSG(静的サイト生成)**は、開店前に作り置きしておく方式。ビルドのときに全ページのHTMLを作り、あとはCDNから配るだけ。
- **ISR(インクリメンタル静的再生成)**は、作り置きしつつ、傷んできたら裏で作り直す方式。静的の速さを保ちながら、一定時間や合図で中身を更新します。
ここで一番つまずきやすいのが、「サーバーで動く=毎回SSR」ではないこと。Next.js App Routerのページはサーバーで実行されますが、それがそのまま「リクエストごとに作り直す」を意味しません。export const revalidateを付ければ静的に作って後から更新するISRになるし、何も指定しなければ多くのページはビルド時に静的化されます。「サーバーで動く」と「毎回作り直す」は別物、とだけ覚えてください。
CSRは少し特殊な立ち位置です。SSR/SSG/ISRは「サーバーやビルドでHTMLの中身まで作る」のに対し、CSRは中身をブラウザに任せます。だから初回のHTMLが実質空っぽになりやすい。これがSEOで効いてきます。
速さ・SEO・運用で並べた比較表
4方式を、現場で効く軸だけで並べます。
| 観点 | CSR | SSG | ISR | SSR |
|---|---|---|---|---|
| HTMLを作る時点 | ブラウザ実行時 | ビルド時 | ビルド時+再検証時 | リクエスト時 |
| 初回表示の速さ | 遅くなりがち | 最速 | 最速級 | 中(サーバー次第) |
| データ鮮度 | 最新(取得すれば) | ビルド時点 | 秒数orイベント単位 | ほぼ最新 |
| SEO/AI回答での拾われやすさ | 弱い | 強い | 強い | 強い |
| 配信コスト | 低(静的+API) | 低 | 中 | 高くなりやすい |
| 向くページ | 管理画面、社内ツール | 記事、ヘルプ、LP | 商品一覧、ニュース | ダッシュボード、検索、カート |
| よくある落とし穴 | 初回が空でSEO弱 | ビルドが長い | 古い表示が少し残る | キャッシュ無しで遅い |
表で見ると、CSRだけ毛色が違うのが分かります。検索流入やAIへの引用を狙う公開ページでCSRを選ぶと、クローラーが空のHTMLを見て「中身がないページ」と判断しがち。逆に、ログインしないと見られない管理画面なら、SEOは要らないのでCSRで身軽に作るのも十分アリです。SEOが要らない場面まで律儀にSSRにする必要はありません。
「鮮度・個人化・更新頻度・コスト」で決める
方式は気分で選ぶものではなく、ページの性質から逆算します。僕がいつも見るのはこの4軸です。
- 鮮度はどこまで必要か。 公開時に確定するなら静的でいい。秒単位で変わるなら動的を混ぜる。
- 個人化はあるか。 「あなた専用」の表示(残高、未読、権限)が本文に入るならSSR。誰が見ても同じならSSG/ISR。
- 更新頻度はどれくらいか。 月に数回ならSSGの再ビルドで足りる。数分おきならISR。リアルタイムならSSR。
- コストはどこに乗るか。 SSRはリクエストごとにサーバーが動くので、アクセスが増えるほど費用も増える。静的は配るだけなので安い。
この順で当てはめると、答えはだいたい一意に決まります。3つ具体例で見ます。
例1:ブログ記事やLP。 本文・OGP・内部リンクが公開時に固定されるならSSG一択。広告位置やCTAを変えたくなっても、記事を再ビルドしてCDNに載せ直せば済みます。JavaScriptを減らす設計はClaude Codeでコード分割を進める方法も合わせて読むと効きます。
例2:ECの商品一覧。 価格や在庫が数分単位で動くならISR。読者には静的の速さを見せつつ、CMS更新や在庫変更のあとにrevalidatePathやrevalidateTagで裏側だけ作り直せます。
例3:会員ダッシュボード。 Cookie、権限、請求状況、未読通知。ユーザーごとに変わる情報が本文に入るならSSRにします。冒頭の「残高チラ見え」は、まさにここをSSGにしようとした事故でした。
Next.jsで方式を切り替える(SSRとISR)
Next.js App Routerで、同じ「サーバーで動くページ」が方式を変える様子を、コピペで動く形で見ます。dummyjson.comを使うのでAPIキーは不要です。
まずSSR。cache: "no-store"を付けると、ビルド時に固定されずリクエストのたびに取りに行く意図がはっきりします。
// app/products/[id]/page.tsx ← リクエストごとに最新を取るSSR
import { notFound } from "next/navigation";
type Product = { id: number; title: string; price: number; stock: number };
async function getProduct(id: string): Promise<Product | null> {
// no-store を付けると毎回サーバーで取得=動的レンダリングになる
const res = await fetch(`https://dummyjson.com/products/${id}`, {
cache: "no-store",
});
if (res.status === 404) return null;
if (!res.ok) throw new Error(`商品取得に失敗: ${res.status}`);
const data = (await res.json()) as Product;
return data;
}
export default async function ProductPage({
params,
}: {
// App Router では params は Promise として受け取る(現行の型)
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
if (!product) notFound();
return (
<main>
<h1>{product.title}</h1>
<p>価格: {product.price.toLocaleString("ja-JP")} 円</p>
<p>在庫: {product.stock}</p>
</main>
);
}
次にISR。export const revalidateとgenerateStaticParamsを足すだけで、静的に作りつつ一定時間で裏更新に変わります。差分はそこだけです。
// app/catalog/[id]/page.tsx ← 静的に作って1時間ごとに裏で作り直すISR
import { notFound } from "next/navigation";
// このページは ISR。1時間ごとに最大1回、裏側で作り直す
export const revalidate = 3600;
type Product = { id: number; title: string; description: string };
// ビルド時に作っておくページを指定する
export async function generateStaticParams() {
return ["1", "2", "3"].map((id) => ({ id }));
}
async function getProduct(id: string): Promise<Product | null> {
const res = await fetch(`https://dummyjson.com/products/${id}`, {
// tags を付けておくと revalidateTag("product:1") でピンポイント更新できる
next: { revalidate: 3600, tags: [`product:${id}`] },
});
if (res.status === 404) return null;
if (!res.ok) throw new Error(`商品取得に失敗: ${res.status}`);
return (await res.json()) as Product;
}
export default async function CatalogPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
if (!product) notFound();
return (
<article>
<h1>{product.title}</h1>
<p>{product.description}</p>
</article>
);
}
ここで一つ、現行Next.jsの大事な仕様を。revalidateを0にしたりcache: "no-store"を使うと、そのルートは静的化されず動的レンダリング(実質SSR)に倒れます。「ISRのつもりで0秒にしていたら、ただのSSRで負荷が戻っていた」は本当によくある勘違いです。鮮度が要るなら短い秒数で粘らず、revalidatePath/revalidateTagのオンデマンド更新に切り替えるのが定石です(Next.js公式のISRガイド)。
Next.jsで完全な静的配信にする
ISRより一歩進めて、Node.jsサーバーなしで配れる「完全静的」も整理しておきます。output: "export"を付けると、next buildがルートごとのHTMLを吐き、outフォルダをそのまま静的ホスティングに置けます。
// next.config.mjs ← Cloudflare Pages などへ静的配信する設定
const nextConfig = {
output: "export",
images: { unoptimized: true },
};
export default nextConfig;
ただし静的エクスポートとISRは同居できません。公式も明確に「ISR is not supported when creating a Static Export(ISRは静的エクスポートではサポートされない)」と書いています。Cookie前提の表示、オンデマンドAPI、ISRが欲しくなった時点で、その設計は静的ホスティングだけでは完結しません。「Cloudflare Pagesに静的で置くのか、Node/VercelでSSRを動かすのか」を最初に決めてからClaude Codeに渡すと、後戻りが激減します。
Astroでデフォルト静的+必要な所だけ動的
Astroは思想が逆で、デフォルトが静的生成。全ページをまずSSGで作り、リクエスト時に動かしたいルートだけexport const prerender = falseを付けます。Astroをこのブログでどう回しているかはAstroのIslandsで爆速サイトを作る話に詳しく書きました。
まず静的ページ。getStaticPathsで作るURLを並べます。
---
// src/pages/docs/[slug].astro ← ビルド時に生成される静的ページ
export async function getStaticPaths() {
const docs = [
{ slug: "ssr", title: "SSRの手引き" },
{ slug: "ssg", title: "SSGの手引き" },
];
// params と props を返すと、その分だけHTMLが事前生成される
return docs.map((doc) => ({ params: { slug: doc.slug }, props: { doc } }));
}
const { doc } = Astro.props;
---
<html lang="ja">
<body>
<article>
<h1>{doc.title}</h1>
<p>このページはビルド時に作られました。</p>
</article>
</body>
</html>
次に、同じプロジェクトの中で1ページだけSSRにする例。prerender = falseが目印です。
---
// src/pages/account.astro ← この1ページだけリクエスト時レンダリング
export const prerender = false;
const session = Astro.cookies.get("session")?.value;
const name = session ? "Masa" : "ゲスト";
// 個人化ページはキャッシュさせない
Astro.response.headers.set("Cache-Control", "private, no-store");
---
<html lang="ja">
<body>
<h1>アカウント</h1>
<p>こんにちは、{name} さん。</p>
</body>
</html>
Astroの落とし穴は、一部だけ動的にしたつもりがadapterを入れ忘れて本番で動かないこと。Cloudflare、Netlify、Vercel、Node.jsなど、実行環境に合うadapterが要ります。Claude Codeには「astro.config.mjsとデプロイ先を読んで、prerender = falseが本番で成立するか確認して」と頼むのが安全です。Next.jsとの実務的な使い分けはNext.jsフルスタック開発の実務ガイドも参考になります。
検証コマンドとClaude Codeへの頼み方
レンダリング方式は、コードを眺めるだけでは確信が持てません。ビルド結果、レスポンスヘッダー、再検証ログ、Lighthouseの数値を見て初めて答え合わせができます。
# Next.js: 本番と同じ挙動で確認する
npm run build
npm run start
# 別ターミナルでヘッダーを見る(x-nextjs-cache が効く)
# HIT=キャッシュ / STALE=裏で再生成中 / MISS=未キャッシュ / REVALIDATED=更新直後
curl -I http://localhost:3000/catalog/1
curl -I http://localhost:3000/products/1
# ISR のキャッシュ命中/不命中をログに出す
# (.env に書く) NEXT_PRIVATE_DEBUG_CACHE=1
# Astro
npm run build
npm run preview
x-nextjs-cacheヘッダーの値を見れば、そのページが本当にISRとして効いているか一発で分かります。MISSが出続けるなら静的化に失敗しているサインです。
Claude Codeに頼むときは、実装だけでなく「分類の根拠」と「検証コマンド」を一緒に出させるのがコツ。これをやると、静的でいいページにCookie処理を混ぜるミスを公開前に潰せます。
目的: Next.js/Astroサイトの各ルートを CSR/SSR/SSG/ISR/静的配信 に分類したい。
調べてほしいこと:
- app/ または src/pages/ のルート一覧
- cookies(), headers(), searchParams, cache: "no-store" の使用箇所
- generateStaticParams(), revalidate, output: "export", Astro の prerender 設定
- 記事/商品/会員/検索の各ページで選ぶべき方式
制約:
- 既存のSEOメタデータと内部リンクを壊さない
- 変更前後で npm run build を通す
- ルートごとに「方式・理由・検証コマンド」を表で出す
僕がやらかした失敗3つ
正直に書きます。方式選びでは何度もコケました。
ひとつ目は冒頭の個人化ページのSSG化。「速くして」だけ伝えたら、会員ページまで静的に倒れて、キャッシュ越しに他人の表示が見える寸前でした。以来、個人化が1要素でも入るページは問答無用でSSRと決めています。
ふたつ目はISRの秒数を1秒にしたこと。鮮度を欲張ってrevalidate = 1にしたら、実質ほぼ毎回作り直しで、SSRと変わらない負荷がサーバーに戻ってきました。今は「迷ったら1時間、精度が要るならオンデマンド更新」に統一しています。
みっつ目は静的エクスポートにISRを後付けしようとしたこと。output: "export"のプロジェクトに在庫の自動更新を足そうとして、ビルドが延々と通らず半日溶かしました。設計の入口で「静的で完結か、サーバーありか」を決めなかったツケです。
よくある質問
Q. SSRとSSGの違いを一言で言うと? A. HTMLを「リクエストのたびに作る」のがSSR、「ビルド時に一度作って配るだけ」がSSG。鮮度はSSR、速さと安さはSSGが有利です。
Q. ISRとは結局なんですか?
A. SSGとSSRのいいとこ取りです。静的に配って速さを保ちつつ、一定時間や合図(revalidatePathなど)でページを裏側で作り直します。商品一覧やニュースのように「速いけど、たまに更新したい」ページ向きです。
Q. SEOを重視するならどれ? A. SSGかISR、なければSSRです。中身の入ったHTMLを最初から返せるので、検索エンジンにもAI回答エンジンにも拾われやすい。CSRは初回HTMLが空になりがちで不利なので、公開ページでは避けます。
Q. CSRはもう使わない方がいい? A. いいえ。ログイン必須の管理画面や社内ツールなど、SEOが要らずインタラクションが多い画面ではCSRが軽くて快適です。要は「検索に出したいか」で線を引きます。
Q. Next.jsとAstro、どっちで考えると楽? A. 記事中心ならデフォルト静的のAstroが素直。動的機能やServer Actionsを多用するアプリはNext.jsが楽です。どちらも「必要な所だけ動的」が基本姿勢で、考え方は共通です。
まとめ
CSR/SSR/SSG/ISRは、覚えることが多そうに見えて、核は「HTMLをいつ・どこで作るか」の一点だけ。あとは鮮度・個人化・更新頻度・コストの4軸でページごとに当てはめれば、答えはほぼ自動で決まります。
記事はSSG、商品一覧はISR、会員と検索はSSR。この基本形を出発点に、Next.jsならrevalidateの有無で、Astroならprerenderの有無で切り替える。そして必ずx-nextjs-cacheやビルドログで答え合わせをする。速そうだから、ではなく、ページの性質から選ぶ。冒頭の「残高チラ見え」を二度と起こさない一番の近道は、これでした。
レンダリング戦略を含め、Next.js/Astroの設計・レビュー・計測までチームで整えたい方はClaude Code研修・導入相談が次の入口です。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。