本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

Server と Client の選び方

迷ったら Server Component から書き始めて、状態やイベント、ブラウザ API が必要になった部分だけを Client Component へ切り出します。

既定は Server Component

App Router では、何も書かなければ Server Component です。これは「初期値がたまたまそうなっている」のではなく、そちらを既定にしたほうが良いからそうなっています。

だから判断は「Client Component にするかどうか」の一方向だけです。Server Component にする理由を探す必要はありません。

Client Component が必要になる4つの場合

公式ドキュメントが挙げているのは次の4つです。

必要になるもの例
状態とイベントuseState、onClick、onChange
ライフサイクルuseEffect
ブラウザ専用の APIwindow、localStorage、navigator
それらを使うカスタムフック上の3つを内側で使っているもの

逆に、Server Component が向いているのは次の場合です。

  • データベースや API から、元に近いところで取得する
  • API キーやトークンを、ブラウザに出さずに使う
  • ブラウザへ送る JavaScript を減らす
  • 最初の表示を早くする

判断の順番

迷ったときの決め方。上から順に当てはめる
  1. Server
    まず何も書かずに始める

    App Router の既定は Server Component。理由を探す必要はない。

  2. Browser
    状態・イベント・ブラウザ API が要るか

    要るなら、その部分だけを別ファイルに切り出して use client を書く。

  3. Server
    データ取得は移さない

    取得はページ側に残し、結果を props で渡す。境界を上げない。

  1. まず Server Component として書く。何も書かない
  2. 押せるもの・変わるものが出てきたら、その部分だけ切り出す
  3. 切り出したファイルの先頭に 'use client' を書く
  4. データ取得は移さない。サーバーに残したまま props で渡す

この順番で書くと、境界は自然に葉のほうへ寄ります。逆に、ページの先頭に 'use client' を書いてから始めると、あとで境界を下げ直す作業が必要になります。

位置が変わると、送るコードが変わる

同じ画面を2通りに書いて、配信される成果物を実際に検査しました。

ページ全体を Client Component にした場合

'use client'
 
import { useState } from 'react'
import { priceLabel } from '@/lib/format'
 
export default function Page() {
  const [n, setN] = useState(1200)
  return <button onClick={() => setN(n + 1)}>{priceLabel(n)}</button>
}

押せるボタンだけを Client Component にした場合

import { LikeButton } from '@/components/LikeButton'
import { priceLabel } from '@/lib/format'
 
export default function Page() {
  return (
    <div>
      {priceLabel(1200)}
      <LikeButton />
    </div>
  )
}

lib/format.ts の実装が、配信される JavaScript に含まれるかどうか。

書き方format.ts のコード
ページ全体を Client に含まれる
ボタンだけを Client に含まれない

'use client' を書いたファイルから import したものは、実装ごとブラウザへ運ばれます。重い整形処理や日付ライブラリを使っている画面ほど、この差は大きくなります。

'use client' の数ではなく、位置が効きます。

データ取得は移さない

境界を下げても、データの取得まで一緒に下げてはいけません。

// ❌ ボタンのために全部をブラウザへ持っていった
'use client'
export default function Page() {
  const [items, setItems] = useState([])
  useEffect(() => { fetch('/api/items').then(...) }, [])
}
// ✅ 取得はサーバー、押す部分だけブラウザ
export default async function Page() {
  const items = await db.item.findMany()
  return <List items={items} action={<LikeButton />} />
}

上は、往復が1つ増え、API を1本作る必要があり、ADMIN_KEY のような値も使えなくなります。下は何も増えません。

「Client の中に Server を置きたい」と思ったら

それは境界の引き方を間違えているか、children で解決できるかのどちらかです。import では解決しません。

書き方はClient Component の中に Server Component を置くにあります。

迷ったときの3つの質問

この機能はどちら側か
やりたいことどちら理由
一覧を DB から出すServer取得も整形もサーバーで済む
ボタンで開閉するClient状態が要る
フォームを送って保存するServerform の action に Server Function を渡す
入力に応じて絞り込むClient打つたびに反応が要る
日付を整形して出すServer重いライブラリをブラウザへ送らずに済む
保存した設定を復元するClientlocalStorage は useEffect の中で読む
  1. その処理はブラウザで実行される必要があるか。無ければサーバーに置く
  2. その値はブラウザに届いて困らないか。困るなら渡さない
  3. 切り出せる一番小さい単位はどこか。そこに 'use client' を書く

use client そのものの意味はuse client とは何かに、ブラウザへ届くものの中身はブラウザに何が届くかにまとめてあります。

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

では、このコードはどうなりますか。

記事一覧に「前回開いた並び順を localStorage から読んで復元する」機能を足します。どこに書きますか。

この問題を解くこのテーマの問題 3 問