Interactive

レンダリング戦略

レンダリング方式の特性を理解してClaudeCodeに適切な指示を与えることで、パフォーマンスと開発効率を両立できます。弊社では管理画面ではSPAを優先し、公開ページではSSRとCloudflareキャッシュの組み合わせを基本とします。

弊社の推奨ルール

  • 管理画面やダッシュボードはSPA(Single Page Application)で実装
  • 公開ページはSSR + Cloudflareキャッシュでパフォーマンス最適化
  • ISRは基本的に採用せず、キャッシュ制御はCloudflareで統一管理
  • ハイドレーションエラーは開発段階で徹底的に防ぐ
  • 完全静的サイトのみSSGを採用

ClaudeCodeでの利用

「管理画面なのでSPAで実装して、初期ローディングUIも追加」
「このLPはSSRで実装、Cloudflareでキャッシュ制御」
「ドキュメントサイトは完全静的なのでSSGで」
「商品詳細ページはSSR必須、ハイドレーション対策も含めて」

SPA(Single Page Application)を管理画面で使う

内部向けツールや管理画面では、開発効率とユーザー体験を重視してSPAを採用します。ページ遷移が高速で、リッチなインタラクションを実装しやすいのが特徴です。

// 管理画面はSPAで実装
'use client';
export default function AdminDashboard() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // 認証後にデータ取得
    fetchDashboardData()
      .then(setData)
      .finally(() => setLoading(false));
  }, []);
  
  if (loading) return <LoadingUI />;
  return <DashboardLayout data={data} />;
}

SPAを選ぶ基準:認証が必要、SEO不要、インタラクティブ性重視、ページ遷移の高速化が必要な場合です。

SSR(Server-Side Rendering)をSEO重要ページで使う

商品詳細ページやランディングページなど、SEOとパフォーマンスが重要な場合はSSRを採用します。初回HTMLに全コンテンツが含まれるため、検索エンジンのクロールに最適です。

// 商品詳細ページはSSRでSEO対策
export default async function ProductPage({ params }) {
  const product = await fetchProduct(params.id);
  
  // Cloudflareでキャッシュ制御
  headers({
    'Cache-Control': 'public, max-age=60, s-maxage=300',
    'CDN-Cache-Control': 'max-age=3600',
    'Surrogate-Key': `product-${params.id}`, // パージ用
  });
  
  return <ProductDetail product={product} />;
}

SSRを選ぶ基準:SEOが必須、動的コンテンツ、初回表示速度が重要、OGP対応が必要な場合です。

SSG(Static Site Generation)を完全静的サイトで使う

ドキュメントサイトやコーポレートサイトなど、更新頻度が極めて低く完全に静的なコンテンツにはSSGを使用します。全ページを事前生成するため、最高速の配信が可能です。

// ドキュメントは全ページ事前生成
export async function generateStaticParams() {
  const docs = await fetchAllDocuments();
  return docs.map(doc => ({
    category: doc.category,
    slug: doc.slug,
  }));
}

export default async function DocPage({ params }) {
  const content = await fetchDocument(params.category, params.slug);
  return <Documentation content={content} />;
}

SSGを選ぶ基準:コンテンツが完全に静的、更新時は再ビルドで問題ない、最高速の配信が必要な場合です。

ISRを使わない理由とSSR + Cloudflareで代替

Next.jsのISR(Incremental Static Regeneration)は技術的には優れていますが、弊社では基本的に採用しません。キャッシュ制御をオリジンサーバーではなくCloudflareで統一管理したいためです。

// 推奨しない:ISRでオリジンサーバーがキャッシュ管理
export default async function BlogPage({ params }) {
  const article = await fetchArticle(params.slug);
  return <Article content={article} />;
}
export const revalidate = 3600; // オリジンサーバーで管理

// 推奨:SSR + Cloudflareでエッジキャッシュ管理
export default async function BlogPage({ params }) {
  const article = await fetchArticle(params.slug);
  
  // Cloudflareで一元管理
  headers({
    'Cache-Control': 'public, max-age=60',
    'Cloudflare-CDN-Cache-Control': 'max-age=3600',
    'Surrogate-Key': `blog-${params.slug}`,
  });
  
  return <Article content={article} />;
}

ISRを使わないメリット:キャッシュの挙動が予測可能、Cloudflareで統一的な制御、パージが簡単、監視・分析が一元化できます。

ハイドレーションエラーを防ぐ設計

SSRを使う場合、サーバーとクライアントでHTMLが不一致になるハイドレーションエラーに特に注意が必要です。

// 推奨しない:ハイドレーションエラーが発生
function Component() {
  // ランダム値や日時はサーバーとクライアントで異なる
  const id = Math.random();
  const now = new Date().toISOString();
  
  return <div id={id}>{now}</div>;
}

// 推奨:クライアントサイドでのみ動的値を使用
function Component() {
  const [mounted, setMounted] = useState(false);
  const [dynamicValue, setDynamicValue] = useState('');
  
  useEffect(() => {
    setMounted(true);
    setDynamicValue(new Date().toISOString());
  }, []);
  
  return (
    <div>
      {mounted ? dynamicValue : 'Loading...'}
    </div>
  );
}

ハイドレーションエラーの主な原因:日時、ランダム値、ブラウザ専用API、条件分岐の不一致です。

Cloudflareキャッシュ戦略

SSRとCloudflareを組み合わせることで、ISRと同等以上の効果を実現します。オリジンサーバーの負荷を最小限に抑えながら、高速配信が可能です。

// ブログ記事のキャッシュ戦略
export default async function ArticlePage({ params }) {
  const article = await fetchArticle(params.slug);
  
  // 段階的なキャッシュ設定
  headers({
    // ブラウザキャッシュは短め
    'Cache-Control': 'public, max-age=60',
    // CDNキャッシュは長め
    'Cloudflare-CDN-Cache-Control': 'max-age=86400',
    // カテゴリー別のパージ管理
    'Surrogate-Key': `article article-${params.slug} category-${article.category}`,
  });
  
  return <Article content={article} />;
}

// 更新時はCloudflare APIでパージ
async function purgeArticleCache(slug: string) {
  await fetch('https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache', {
    method: 'POST',
    headers: {
      'X-Auth-Email': process.env.CF_EMAIL,
      'X-Auth-Key': process.env.CF_API_KEY,
    },
    body: JSON.stringify({
      tags: [`article-${slug}`],
    }),
  });
}

キャッシュ戦略のポイント:静的アセットは長期、動的コンテンツは短期、タグベースで細かくパージ制御できます。

よくある問題

SPAでSEOが必要になった

後からSEOが必要になった場合は、Next.jsのdynamic importやメタタグの動的生成で部分的に対応します。重要なページから段階的にSSRに移行します。

キャッシュが更新されない

Cloudflareのパージ APIを使用して明示的にキャッシュをクリアします。Surrogate-Keyを活用して関連するページを一括でパージすることも可能です。

ハイドレーションエラーが解決できない

suppressHydrationWarningは根本解決にならないため使用しません。useEffectでクライアント専用の処理を分離するか、dynamic importでSSRを無効化します。

SSRのパフォーマンスが悪い

データ取得の並列化、Streaming SSR、部分的なキャッシュ、Edge Functionsの活用で改善します。Cloudflareのキャッシュ設定を見直すことも重要です。

レンダリング戦略の選択に迷う

管理画面はSPA、公開ページはSSR + Cloudflareキャッシュ、完全静的サイトはSSGという基本方針に従います。ISRは避けてCloudflareで統一管理します。