Next.js

レンダリング戦略

Next.js

SSR・SSG・ISR・Streaming

レンダリング戦略

SSG・SSR・ISR・Streaming の使い分け

rendering.tsx tsx
// === 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を改善

app/dashboard/page.tsx tsx
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' の境界とインタラクティブ性

components.tsx tsx
// === 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>
    );
}