Client Component とは何か
Client Component は、ファイルの先頭に use client と書くことでブラウザにも送られる React コンポーネントです。
#いつ必要になるのか
判断は単純です。ブラウザにしかないものが必要かどうかだけを見ます。
| 必要なもの | Client にする |
|---|---|
useState useReducer などの状態 | 要る |
useEffect | 要る |
onClick onChange などのイベント | 要る |
window document localStorage | 要る |
| ブラウザ専用のライブラリ | 要る |
| 表示するだけ | 要らない |
| データを取ってきて並べるだけ | 要らない |
迷ったら「押せるか」「変わるか」で考えてください。押せる/変わるならブラウザが必要です。
#書き方
ファイルのいちばん上、import より前に一度だけ書きます。
'use client'
import { useState } from 'react'
export function Counter() {
const [n, setN] = useState(0)
return <button onClick={() => setN(n + 1)}>{n}</button>
}
#サーバーでも動くことに注意
名前から「ブラウザだけで動く」と思いがちですが、違います。Client Component も、最初の表示のためにサーバー側で一度実行されます。
1. サーバーで実行 ──▶ HTML を作る(初回表示が速い)
2. ブラウザへ JavaScript が届く
3. ブラウザで実行 ──▶ 押せるようになる(hydration)
だから Client Component の中に window.innerWidth をそのまま書くと、手順1で落ちます。ブラウザ限定の処理は useEffect の中に置いてください。
**「Client Component = ブラウザだけ」ではなく「Client Component = ブラウザにも送られる」**と覚えるほうが正確です。
#境界は下にだけ伝わる
'use client' を書いたファイルから import したものは、すべて Client Component になります。逆はありません。親には影響しません。
Page(Server)
└─ Panel('use client') ← ここから下が Client
└─ Badge ← Client になる
だから、付ける位置が上へ行くほど、ブラウザへ送る JavaScript が増えます。
#大きくなってきたら children で逃がす
Client Component の中に重い表示を抱えたくなったら、import するのではなく children として外から渡します。渡されたものは境界に巻き込まれません。
// app/page.tsx(Server のまま)
export default async function Page() {
const items = await db.item.findMany()
return (
<Panel>
<ItemList items={items} /> {/* Server のまま */}
</Panel>
)
}
Panel の中で import { ItemList } と書くと、その瞬間に ItemList も Client になります。children で渡すか import するかで結果が変わるのがここです。
#受け取れる props には制限がある
Server から Client へ渡せるのは、値だけです。
- 渡せる: 文字列・数値・配列・プレーンなオブジェクト・
Date・Map・Set - 渡せない: 関数・クラスのインスタンス
関数を渡すとエラーになるので、ハンドラは Client Component の内側で定義します。
#よくある誤解
'use client' を付けたら、そのファイルはブラウザでしか動かない?
動きます。上に書いたとおり、初回はサーバーでも実行されます。
async にできない?
Next.js 16.3.3 ではビルドは通ります。「Client Component は async にできない」と書いてある記事は多いのですが、実際に試すと通ります。ただし読み込み中の表示が扱いにくくなるので、use フックか useEffect を使うほうが素直です。
迷ったら付けておけばいい?
逆です。付けるほどブラウザへ送る JavaScript が増えます。必要なところにだけ付けてください。