Next.js の App Router を使い始めると、多くの方が最初につまずくのが「Server Components(サーバーコンポーネント)」と「Client Components(クライアントコンポーネント)」の違いです。App Router ではコンポーネントは既定でサーバーコンポーネントになり、必要な部分だけを "use client" でクライアントコンポーネントに切り替えます。
この記事では、両者が何をできて何をできないのか、どんなときにクライアントコンポーネントが必要になるのか、そして両者をどう組み合わせるのかを、実際に動くコードとともに解説します。まずは「デフォルトはサーバー側で動く」という前提を押さえていきましょう。
目次
App Router ではコンポーネントは既定でサーバーコンポーネント
App Router(app ディレクトリ)に置いたコンポーネントは、特別な指定をしない限りすべて「サーバーコンポーネント」です。サーバーコンポーネントとは、その名のとおりサーバー側でレンダリングされ、その結果だけがブラウザに送られるコンポーネントを指します。コンポーネントの JavaScript 自体はブラウザに送られないため、ページの読み込みが軽くなるのが大きな特徴です。
たとえば次のようなページコンポーネントは、何も書かなくてもサーバーコンポーネントとして動作します。async を付けて関数の中で直接データを取得できる点に注目してください。
// "use client" が無いので、これはサーバーコンポーネント
// async を付けて、関数の中で直接 await できる
export default async function Page() {
// サーバー側で実行されるので fetch のレスポンスを直接待てる
const res = await fetch("https://api.example.com/posts");
const posts: { id: number; title: string }[] = await res.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
このコンポーネントはブラウザではなくサーバーで実行されます。そのため fetch の結果を await でそのまま待ち受けられ、取得済みの HTML がブラウザに届きます。データ取得のためのローディング状態を useState で管理する、といった手間が要らないのがサーバーコンポーネントの気持ちよさです。
サーバーコンポーネントでできること・できないこと
サーバーコンポーネントは「サーバー側だけで動く」という性質から、得意なことと不得意なことがはっきり分かれます。整理すると、サーバー資源を扱う処理は得意で、ブラウザ上の対話的な処理は扱えません。
できること
サーバーコンポーネントでは、前述のように async 関数として書いてデータを直接取得できます。加えて、データベースへの接続や、外部に漏らしたくない API キーを使った処理など、サーバー側の資源へ安全にアクセスできます。これらのコードはブラウザに送られないため、秘密情報が利用者の画面に露出しません。
import { db } from "@/lib/db";
// サーバーコンポーネントなので DB や API キーに直接触れる
export default async function Dashboard() {
// process.env.API_KEY などの秘密情報はブラウザに送られない
const users = await db.user.findMany();
return (
<div>
<h1>ユーザー一覧</h1>
<p>登録数: {users.length}</p>
</div>
);
}
もう一つの利点が、バンドル(ブラウザに送る JavaScript のかたまり)を軽くできることです。サーバーコンポーネントで使ったライブラリのコードはブラウザに送られないため、重い日付処理ライブラリなどをサーバー側だけで使えば、利用者がダウンロードする量を増やさずに済みます。
できないこと
一方で、サーバーコンポーネントはブラウザ上で動かないため、状態や副作用を扱うフックが使えません。具体的には useState や useEffect といったフックは呼び出せません。また、onClick や onChange のようなイベントハンドラーも登録できず、window や localStorage といったブラウザ API にもアクセスできません。これらを使いたい場合は、次に説明するクライアントコンポーネントの出番です。
クライアントコンポーネントと “use client”
クライアントコンポーネントは、従来の React と同じようにブラウザ側で動くコンポーネントです。作り方は簡単で、ファイルの先頭行に "use client" というディレクティブ(宣言文)を書くだけです。この一行があるファイルは、そのファイル内のコンポーネントがクライアントコンポーネントとして扱われます。
"use client"; // ← 必ずファイルの先頭行に書く
import { useState } from "react";
export default function LikeButton() {
// クライアントコンポーネントなので useState が使える
const [count, setCount] = useState(0);
return (
// onClick などのイベントも登録できる
<button onClick={() => setCount(count + 1)}>
いいね {count}
</button>
);
}
"use client" はファイルの一番上、import 文よりも前に書く必要があります。途中の行に書いてもディレクティブとして認識されないため、必ず先頭に置きましょう。この一行を境に「ここから先はブラウザで動く世界です」と Next.js に伝えているイメージです。
クライアントコンポーネントが必要になる場面
クライアントコンポーネントを選ぶ基準は「ブラウザ上での対話が必要かどうか」です。具体的には、ボタンやフォームなど利用者の操作に反応するインタラクティブな UI、useState で管理する状態、そして window や localStorage といったブラウザ API を扱いたいときです。逆に言えば、これらが不要な表示専用の部分は、無理にクライアントコンポーネントにする必要はありません。
観点ごとの Server と Client の違い
ここまでの内容を、観点ごとに対応させて整理します。どちらを使うか迷ったときは、この表を判断の目安にしてください。
| 観点 | Server Component | Client Component |
|---|---|---|
| 指定方法 | 既定(何も書かない) | 先頭に "use client" |
| 実行される場所 | サーバー | ブラウザ |
| async でのデータ取得 | できる | 基本は使わない |
| useState / useEffect | 使えない | 使える |
| onClick 等のイベント | 使えない | 使える |
| DB / API キーへのアクセス | できる | できない |
| ブラウザ API(window 等) | 使えない | 使える |
| バンドルへの影響 | 軽い(JS を送らない) | JS がブラウザに送られる |
サーバーとクライアントを組み合わせる
実際のアプリでは、両者を組み合わせて使います。基本の形は「サーバーコンポーネントの中に、必要な部分だけクライアントコンポーネントを子として置く」というものです。ページ全体はサーバーで組み立て、ボタンなど対話が必要なところだけをクライアントコンポーネントに切り出します。
// このファイルはサーバーコンポーネント("use client" が無い)
import LikeButton from "./LikeButton";
export default async function Page() {
const res = await fetch("https://api.example.com/article");
const article: { title: string; body: string } = await res.json();
return (
<article>
{/* 表示はサーバーコンポーネントが担当 */}
<h1>{article.title}</h1>
<p>{article.body}</p>
{/* 対話が必要な部分だけクライアントコンポーネントに任せる */}
<LikeButton />
</article>
);
}
ここで大切なのは、"use client" は「境界」だということです。あるファイルに "use client" を書くと、そのコンポーネントと、そこから import して使うコンポーネントはクライアント側に含まれます。つまり "use client" はコンポーネントツリーの中で「ここからクライアント」という線を引く役割を持ちます。上の例では LikeButton とその内部だけがクライアント側で、ページ本体はサーバー側のままです。
この境界を意識すると、無駄にクライアント化する範囲を小さく保てます。表示専用の部分はサーバーコンポーネントに残し、状態やイベントが必要な葉(末端)の部分だけをクライアントコンポーネントにする、という設計が基本方針になります。
まとめ
App Router ではコンポーネントは既定でサーバーコンポーネントであり、サーバー側でデータ取得や DB・API キーへのアクセスができ、バンドルを軽く保てます。一方、useState や useEffect、onClick などのイベント、ブラウザ API を使いたいときは、ファイル先頭に "use client" を書いてクライアントコンポーネントにします。
組み合わせの基本は、サーバーコンポーネントの中に必要な部分だけクライアントコンポーネントを子として置くことです。"use client" は「ここからクライアント」という境界を引くものだと捉え、対話が必要な末端だけをクライアント化することで、軽くて見通しのよいアプリになります。まずは小さなボタンから "use client" を試してみてください。