SSR vs SSG比較: Claude Codeで選ぶNext.js/Astroの最適レンダリング
Next.js/AstroのSSR・SSG・ISR・静的配信をClaude Codeで比較し、実装例、検証コマンド、設計テンプレまで解説。
最初に言葉をそろえる
SSRとSSGの違いは「速いか遅いか」だけで決めると失敗します。SSRはサーバーサイドレンダリング、つまりリクエストが来た時にサーバーでHTMLを作る方式です。SSGは静的サイト生成、つまりビルド時にHTMLを作ってCDNから配る方式です。ISRはIncremental Static Regenerationの略で、静的に配りつつ一定時間や明示的な再検証でページを更新する折衷案です。
初心者が迷いやすいのは、Next.js App Routerではページがサーバーで実行されることと、毎回SSRになることが同じではない点です。公式のDynamic Route Segmentsでは、現在のTypeScript例でparamsをPromiseとして扱います。また、リクエスト時の処理を明示したい場合はconnectionを使えます。
Claude Codeに「このページを速くして」とだけ頼むと、SSGに寄せすぎて在庫や会員表示が古くなることがあります。逆に何でもSSRにすると、初回応答が遅くなり、サーバー費用も増えます。先に「ページの鮮度」「個人化の有無」「更新頻度」「広告・商品導線への影響」を表にしてから依頼するのが安全です。
比較表と3つの実例
| 観点 | SSG | ISR | SSR |
|---|---|---|---|
| HTMLを作る時点 | ビルド時 | ビルド時と再検証時 | リクエスト時 |
| 向くページ | 記事、ヘルプ、LP | 商品一覧、ニュース、カテゴリ | ダッシュボード、検索、カート |
| データ鮮度 | ビルド時点 | 秒数またはイベント単位 | ほぼ最新 |
| 配信コスト | 低い | 中程度 | 高くなりやすい |
| 落とし穴 | ビルドが長い | 古い表示が少し残る | キャッシュしないと遅い |
実例1はブログ記事です。本文、OGP、内部リンクが公開時に固定されるならSSGが合います。広告位置やCTAを変える場合も、記事を再ビルドしてCDNに載せれば十分です。関連して、JavaScript量を減らす話はClaude Codeでコード分割を進める方法も参考になります。
実例2はECの商品ページです。価格や在庫が数分単位で変わるならISRが現実的です。読者には静的ページの速さを出しつつ、CMS更新や在庫更新の後にrevalidatePathやrevalidateTagで更新できます。Next.js公式のISRガイドでは、ISRはNode.js runtimeで使え、静的エクスポートでは使えないと説明されています。
実例3は会員ダッシュボードです。Cookie、権限、請求状況、未読通知など、ユーザーごとに変わる情報を含むならSSRにします。ここをSSGにすると、個人情報の表示漏れや古い権限表示につながります。エッジ配信の考え方はClaude Codeでエッジコンピューティングを設計すると合わせて読むと判断しやすいです。
Next.jsでSSRを書く
次はNext.js App Routerのコピペ用SSR例です。dummyjson.comを使うので、APIキーなしで挙動を確認できます。connection()とcache: "no-store"で、ビルド時に固定されずリクエストごとにデータを取りに行く意図を明確にします。
// app/products/[id]/page.tsx
import { notFound } from "next/navigation";
import { connection } from "next/server";
type Product = {
id: number;
title: string;
price: number;
stock: number;
updatedAt: string;
};
async function getProduct(id: string): Promise<Product | null> {
await connection();
const res = await fetch(`https://dummyjson.com/products/${id}`, {
cache: "no-store",
});
if (res.status === 404) return null;
if (!res.ok) throw new Error(`Failed to load product: ${res.status}`);
const data = (await res.json()) as {
id: number;
title: string;
price: number;
stock: number;
meta?: { updatedAt?: string };
};
return {
id: data.id,
title: data.title,
price: data.price,
stock: data.stock,
updatedAt: data.meta?.updatedAt ?? new Date().toISOString(),
};
}
export default async function ProductPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const product = await getProduct(id);
if (!product) notFound();
return (
<main>
<h1>{product.title}</h1>
<p>Price: ${product.price.toLocaleString("en-US")}</p>
<p>Stock: {product.stock}</p>
<p>Updated: {new Date(product.updatedAt).toLocaleString("ja-JP")}</p>
</main>
);
}
落とし穴は、cookies()やheaders()を便利だからと共通レイアウトへ入れてしまうことです。Next.jsではリクエスト時APIを使うと動的レンダリングに寄ります。記事ページまで巻き込むと、SSGで十分なページがSSR化されます。Claude Codeには「この変更でどのルートが動的化するか」を必ず調べさせます。
SSG、ISR、静的配信を分ける
SSGとISRは「静的に見せる」点では似ていますが、運用は違います。SSGはビルドのたびに作る。ISRは一定時間後、またはイベント後に更新する。完全な静的配信はoutput: "export"でoutフォルダを作り、Node.jsサーバーなしで置ける形にします。公式のStatic Exportsでは、next buildでルートごとのHTMLを生成し、静的アセットを配れると説明されています。
// app/catalog/[id]/page.tsx
import { notFound } from "next/navigation";
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}`, {
next: { revalidate: 3600, tags: [`product:${id}`] },
});
if (res.status === 404) return null;
if (!res.ok) throw new Error(`Failed to load product: ${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.config.mjs
const nextConfig = {
output: "export",
images: {
unoptimized: true,
},
};
export default nextConfig;
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"export:check": "next build && npx serve out"
}
}
注意点は、静的エクスポートとISRを混ぜないことです。output: "export"は静的ホスティング向けなので、オンデマンド再検証やサーバー関数前提の機能とは相性がありません。Claude Codeには「Cloudflare Pagesに静的配置するのか、Node/VercelでSSRを動かすのか」を先に伝えます。
AstroでSSGとオンデマンドを分ける
Astroはデフォルトが静的生成です。公式のOn-demand renderingでは、ページやAPI endpointは標準でビルド時に事前生成され、必要なルートだけexport const prerender = falseでリクエスト時レンダリングにできます。
---
// src/pages/docs/[slug].astro
export async function getStaticPaths() {
const docs = [
{ slug: "ssr", title: "SSR guide" },
{ slug: "ssg", title: "SSG guide" },
];
return docs.map((doc) => ({
params: { slug: doc.slug },
props: { doc },
}));
}
const { doc } = Astro.props;
---
<html lang="ja">
<body>
<article>
<h1>{doc.title}</h1>
<p>This page was generated at build time.</p>
</article>
</body>
</html>
---
// src/pages/account.astro
export const prerender = false;
const session = Astro.cookies.get("session")?.value;
const name = session ? "Masa" : "Guest";
Astro.response.headers.set("Cache-Control", "private, no-store");
---
<html lang="ja">
<body>
<h1>Account</h1>
<p>Hello, {name}.</p>
</body>
</html>
Astroの落とし穴は、静的サイトの一部だけ動的にしたつもりが、adapter設定を忘れて本番で動かないことです。Cloudflare、Netlify、Vercel、Node.jsなど、使う実行環境に合うadapterを選びます。Claude Codeには「astro.config.mjsとデプロイ先を読んで、prerenderの指定が本番で成立するか確認して」と頼むとよいです。
検証コマンドとClaude Code依頼テンプレ
レンダリング方式はコードを見ただけでは判断しきれません。ビルド結果、レスポンスヘッダー、再検証ログ、LighthouseのTTFB/LCPを見ます。Next.jsのISRは公式にもnext buildからnext startで本番挙動を確認する手順が紹介されています。
# Next.js: production behavior
npm run build
npm run start
# In another terminal
curl -I http://localhost:3000/catalog/1
curl -I http://localhost:3000/products/1
# ISR cache debugging
NEXT_PRIVATE_DEBUG_CACHE=1 npm run start
# Astro
npm run build
npm run preview
Claude Codeへは、次のように依頼します。ポイントは、実装だけでなく「判断表」「触ってよい範囲」「検証証拠」を同時に出させることです。
目的: Next.js/Astroサイトの各ルートをSSR、SSG、ISR、静的配信に分類したい。
確認してほしいこと:
- app/ または src/pages/ のルート一覧
- cookies(), headers(), searchParams, connection(), cache: "no-store" の使用箇所
- generateStaticParams(), revalidate, output: "export", Astro prerender の設定
- 収益導線ページ、会員ページ、記事ページ、商品ページで選ぶべき方式
制約:
- 既存のSEOメタデータと内部リンクを壊さない
- 変更前後で npm run build を通す
- 変更したルートごとに、理由と検証コマンドを表で出す
具体的な落とし穴
一つ目は、SSG記事の中でユーザー固有の表示をサーバー側に入れることです。おすすめ商品や会員名をページ本体へ混ぜると、静的化できません。必要なら記事本体はSSG、個人化部分はクライアント側や小さな動的コンポーネントへ分けます。
二つ目は、ISRの秒数を短くしすぎることです。revalidate = 1のような設定は、実質的にサーバーへ負荷を戻します。公式ガイドも、精密さが必要ならオンデマンド再検証、リアルタイム性が必要なら動的レンダリングを検討する流れを示しています。
三つ目は、静的エクスポートで使えない機能を後から足すことです。認証、Cookie前提の表示、オンデマンドAPI、ISRが必要なら、静的ホスティングだけで完結させる設計を見直します。
四つ目は、Claude Codeの提案を「速そう」という理由だけで採用することです。SEO記事なら速度だけでなく、OGP、構造化データ、内部リンク、広告表示、CTAのクリック計測まで見ます。実装後はClaude Codeでパフォーマンス最適化の観点で、LCPとCLSも確認してください。
収益導線まで含めた結論
ClaudeCodeLabのような記事メディアでは、基本はSSGです。記事、比較表、チュートリアル、CTAは静的に配り、商品在庫や会員向けページだけSSRまたはISRへ寄せます。これで検索流入の速度を保ちながら、収益に近い導線だけ更新しやすくできます。
無料で始めるならClaude Codeチートシートで日常コマンドを固め、記事制作や検証テンプレートを増やすならプロダクト一覧を見てください。チームでNext.js/Astroのレンダリング戦略、権限、レビュー、計測まで整える場合はClaude Code研修・導入相談が次の入口です。
実際に試した結果
この記事を更新するにあたり、Next.jsのDynamic Route Segments、connection、ISR、Static Exports、Astroのon-demand rendering、Claude Code overviewを確認しました。Masaの運用では、記事本文をSSG、商品一覧をISR、会員・検索系をSSRに分けた時が一番安定しました。特に効果があったのは、Claude Codeへ「全ルートの分類表」と「検証コマンド」を先に作らせたことです。実装前に判断理由を表にすると、静的でよいページへCookie処理を混ぜるミスを早く見つけられ、公開前レビューの手戻りも減りました。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。