レンダリング戦略
Next.js
SSR・SSG・ISR・Streaming
レンダリング戦略
SSG・SSR・ISR・Streaming の使い分け
// === SSG (Static Site Generation) ===
// fetch に cache: 'force-cache'(デフォルト)→ ビルド時に生成
export default async function BlogPage() {
const posts = await fetch('/api/posts').then(r => r.json()); // ビルド時のみ実行
return <PostList posts={posts} />;
}
// === SSR (Server-Side Rendering) ===
// cache: 'no-store' → 毎リクエストごとにサーバーで生成
export default async function LivePage() {
const data = await fetch('/api/live', { cache: 'no-store' }).then(r => r.json());
return <LiveDashboard data={data} />;
}
// === ISR (Incremental Static Regeneration) ===
// next: { revalidate: N } → N秒ごとにバックグラウンドで再生成
export default async function NewsPage() {
const news = await fetch('/api/news', { next: { revalidate: 60 } }).then(r => r.json());
return <NewsList news={news} />;
}
// === Dynamic vs Static の強制 ===
export const dynamic = 'force-static'; // 強制的にSSG
export const dynamic = 'force-dynamic'; // 強制的にSSR
export const revalidate = 3600; // ページ全体のrevalidate間隔Streaming と Suspense
段階的なUIの表示でTTFBを改善
import { Suspense } from 'react';
// Streaming: 遅いコンポーネントを Suspense でラップ
// → 速いコンポーネントを先に送信し、遅いものを後から流す
export default function Dashboard() {
return (
<div className="grid grid-cols-3 gap-4">
{/* 即座に表示 */}
<WelcomeHeader />
{/* DBクエリが遅いコンポーネント → ローディングUIを先に表示 */}
<Suspense fallback={<StatsSkeleton />}>
<Stats /> {/* async コンポーネント */}
</Suspense>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart /> {/* 独立して読み込み */}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<LatestOrders /> {/* 他と並列で読み込み */}
</Suspense>
</div>
);
}
// loading.tsx は Suspense の自動ラッパー
// app/dashboard/loading.tsx → DashboardPage 全体が Suspense でラップされるServer / Client Component の使い分け
'use client' の境界とインタラクティブ性
// === Server Component(デフォルト)===
// ✅ DB・ファイルシステムに直接アクセス可
// ✅ API キーなど機密情報を使える
// ✅ バンドルサイズに影響しない
// ❌ useState / useEffect / イベントハンドラー不可
// ❌ ブラウザ API 不可
async function ServerCard() {
const data = await db.query(); // ✅ 直接DBアクセス
return <div>{data.title}</div>;
}
// === Client Component ===
// 必要な場合のみ 'use client' を追加
'use client';
import { useState } from 'react';
// ✅ インタラクション・useState・useEffect
// ✅ ブラウザ API(localStorage など)
// ❌ async/await をトップレベルで使えない
function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
// パターン: Server Component が Client Component を children で渡す
// → Server Component の利点を保ちながらインタラクションを追加
function Page() { // Server Component
return (
<ClientShell> {/* 'use client' */}
<ServerData /> {/* Server Component を children として渡せる */}
</ClientShell>
);
}