BeaSkyblue

Next.js 読解メモ 02: Server/Client 境界("use client")

· 3 min read

要点(90秒)

  • デフォルトはサーバーコンポーネント(App Router)。ブラウザ側の機能が必要なときだけファイル先頭に "use client"
  • クライアントで“できること”:イベント(onClick 等)、useState/useEffectwindow/localStorageuseSearchParams(next/navigation)などブラウザAPI
  • クライアントで“できないこと”:サーバー専用処理(DB直叩き、秘密ENV利用、重いNode依存)。
  • 境界の伝播"use client" を付けたファイルから辿る import は全てクライアント側にバンドルされる。
    → サーバー専用モジュールを誤って import するとエラー。
  • 設計指針データ取得はサーバーで、UI操作はクライアントで。上位をクライアント化しない(JS肥大を防ぐ)。

最小パターン(サーバー→クライアント受け渡し)

// app/items/page.tsx  ← サーバー(デフォルト)
export default async function Page() {
  const items = await fetch("https://api.example.com/items", { cache: "force-cache" })
    .then(r => r.json());
  return <ClientList items={items} />; // 値だけ渡す
}

// app/items/ClientList.tsx  ← クライアント
"use client";
import { useState } from "react";

export default function ClientList({ items }: { items: { id:number; name:string }[] }) {
  const [q, setQ] = useState("");
  const filtered = items.filter(i => i.name.includes(q));
  return (
    <>
      <input value={q} onChange={(e)=>setQ(e.target.value)} placeholder="filter..." />
      <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>
    </>
  );
}

ページ自体をクライアント化したいとき(必要最小限だけ)

// app/search/page.tsx  ← どうしてもCSRで良い場面のみ
"use client";
import { useEffect, useState } from "react";

export default function Page() {
  const [data, setData] = useState<any[] | null>(null);

  useEffect(() => {
    const ac = new AbortController();
    fetch("/api/search?q=next", { signal: ac.signal })
      .then(r => r.json()).then(setData)
      .catch(()=>{});
    return () => ac.abort();
  }, []);

  if (!data) return <p>loading...</p>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

注意:ページ丸ごとの "use client" は初期HTMLが薄くなりSEOに不利。末端だけクライアント化が基本。

アンチパターン(避けたい)

  • app/layout.tsx"use client" を付ける → 配下が広くクライアント化されて JSが巨大化
  • クライアントから秘密ENVやDBにアクセス → 露出・セキュリティ事故。必要なら app/api/*/route.ts の薄いプロキシで隠す。
  • サーバーコンポーネント内で window/document を参照 → 実行環境が違うためエラー。

見分け方10秒

  • ファイル先頭に "use client" がある? → クライアント
  • イベント/useState を使っている? → クライアント"use client" 必須)
  • 秘密ENVやDB、重いNode依存? → サーバーに留める

まとめ

  • 原則サーバー、末端だけクライアント
  • "use client"必要最小限に。
  • 値はサーバー→クライアントへ props で渡すのが安全・高速。