Next.js 読解メモ 02: Server/Client 境界("use client")
· 3 min read
要点(90秒)
- デフォルトはサーバーコンポーネント(App Router)。ブラウザ側の機能が必要なときだけファイル先頭に
"use client"。 - クライアントで“できること”:イベント(
onClick等)、useState/useEffect、window/localStorage、useSearchParams(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 で渡すのが安全・高速。