🌊

Next.jsで一行目に書く'use client'とは何なのか

に公開
3

はじめに

こんにちは、Saku です。

もしかしたら、今更?と思われるかもしれませんが、Next.js のアプリケーション開発をしていて、「あれ、この'use client'って一体何?」と疑問に思ったことはありませんか?

Next.js 13 で App Router が導入されて以降、コンポーネントのレンダリングに対するアプローチの仕方が変わりました。その中心的な役割を担うのが、ファイルの先頭に記述する'use client'というディレクティブです。

本記事では、'use 〇〇'にまつわる基本的な知識を整理していきます。

'use client'とは?

'use client'は、「このファイルおよび、ここからインポートされる全てのモジュールはクライアントサイドで実行されるコンポーネント(Client Component)である」と宣言するためのディレクティブです。

要は、「レンダリングはサーバーさんではなく、俺(クライアント)の側でやれよ」という、俺を選べよ宣言なわけです。

app/components/Counter.js
'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

Next.js の App Router では、デフォルトで全てのコンポーネントがサーバーサイドでレンダリングされる Server Componentとして扱われます。これにより、サーバー側でデータ取得や HTML の生成が完了するため、ページの初期表示が高速になるというメリットがあります。

しかし、ユーザーの操作に応じて状態が変わるようなインタラクティブな UI を実装するには、ブラウザ(クライアントサイド)で JavaScript を実行する必要があります。そこで'use client'を使い、特定のコンポーネントを Client Component として明示的に指定するのです。

なぜ導入されたのか?

'use client'は、2022 年 10 月に発表された Next.js 13 および React Server Components(RSC)の思想と共に導入されました。

導入の背景には、こんな課題がありました

  • パフォーマンスの向上: 従来の Next.js では、ページ全体の JavaScript をクライアントに送信していました。しかし実際には、多くのコンポーネントはインタラクティブな機能を必要としません。必要な部分だけをクライアントで動かすことで、バンドルサイズを大幅に削減できます。
  • サーバーリソースの有効活用: データベースアクセスやファイル操作など、サーバーでしかできない処理を効率的に行えるようになりました。
  • 開発体験の改善: 「これはサーバーで動く」「これはクライアントで動く」が明確になることで、コードの意図がわかりやすくなります。

Server Components vs Client Components

'use client'の役割を理解するには、Server Components と Client Components の違いを把握することが重要です。

特徴 Server Component (デフォルト) Client Component ('use client')
実行場所 サーバー クライアント(ブラウザ)
状態管理 (useState) × 利用不可 ⚪︎ 利用可能
ライフサイクル (useEffect) × 利用不可 ⚪︎ 利用可能
イベントハンドラ (onClick など) × 利用不可 ⚪︎ 利用可能
ブラウザ API (window, localStorage) × 利用不可 ⚪︎ 利用可能
データ取得 ⚪︎ async/awaitが利用可能 ⚪︎ 可能(useEffect 内など)
サーバーリソースへのアクセス ⚪︎ 可能(DB、ファイルシステムなど) × 不可(API 経由のみ)

使い分けの基本戦略

基本的な考え方は「まずは全て Server Component で考え、本当にインタラクティブ性が必要な部分だけを Client Component にする」です。これは、パフォーマンスを最優先に考えた設計思想から来ています。

【Server/Client Component の組み合わせイメージ】
静的なコンテンツ(記事本文、ナビゲーション等)は Server Component として構成し、ユーザー操作が必要な部分(いいねボタン、コメントフォーム、検索ボックス等)のみを Client Component として配置する設計が理想的です。

例えば、ブログ記事ページ全体は Server Component で構成し、記事の「いいね」ボタンやコメントフォームだけを Client Component として切り出す、といった設計が理想的です。

app/posts/[slug]/page.js (Server Component)
import LikeButton from '@/app/components/LikeButton'; // Client Component
import CommentForm from '@/app/components/CommentForm'; // Client Component

async function getPost(slug) {
  // サーバーサイドで記事データを取得
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  return res.json();
}

export default async function PostPage({ params }) {
  const post = await getPost(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <LikeButton postId={post.id} />
      <CommentForm postId={post.id} />
    </article>
  );
}

'use client'が必要な具体的な場面

以下の機能を使いたい場合は、'use client'ディレクティブが必須です。

  1. React Hooks (useState, useEffect, useContextなど)
    状態管理や副作用、コンテキストの利用はクライアントサイドで行います。

    "use client";
    import { useState, useEffect } from "react";
    // ...
    
  2. イベントハンドラ (onClick, onChangeなど)
    ユーザーのアクション(クリック、入力など)を処理する関数です。

    "use client";
    export default function MyButton() {
      return <button onClick={() => console.log("Clicked!")}>Click me</button>;
    }
    
  3. ブラウザ専用 API (window, localStorage, navigatorなど)
    サーバー環境には存在しないブラウザの機能を利用する場合です。

    "use client";
    import { useEffect } from "react";
    export default function LocationComponent() {
      useEffect(() => {
        // コンポーネントがマウントされた後に実行される
        console.log(window.location.href);
      }, []);
      return <p>Check the console for the current URL.</p>;
    }
    
  4. サードパーティライブラリ
    内部で上記の Hooks や API に依存している多くのライブラリ(例: react-beautiful-dnd, swiperなど)も Client Component 内で使用する必要があります。

レンダリングの裏側

'use client'がどのようにレンダリングに影響を与えるのか、そのフローを見てみましょう。

【レンダリングフローの概要】
サーバー → クライアントの流れ:

  1. サーバーで Server Component 実行・HTML 生成

  2. Client Component 箇所にプレースホルダー配置

  3. 初期 HTML と JavaScript バンドルをクライアントに送信

  4. ブラウザで HTML 表示後、ハイドレーションで完全にインタラクティブに

  5. サーバーサイド

    1. React がリクエストを受け取り、Server Component ツリーのレンダリングを開始します。
    2. Server Component は実行され、HTML に変換されます。
    3. Client Component に遭遇すると、その場所には**プレースホルダー(目印)**と、必要な props(シリアライズ可能な値のみ)を埋め込みます。Client Component 自体のコードは実行しません。
    4. 完成した初期 HTML と、Client Component 用の JavaScript バンドルへの参照をクライアントに送信します。
  6. クライアントサイド

    1. ブラウザがサーバーから受け取った HTML を解析し、静的な UI を素早く表示します。
    2. 次に、Client Component 用の JavaScript バンドルをダウンロード・実行します。
    3. ハイドレーションというプロセスが実行され、サーバーが生成した静的な HTML に、JavaScript のロジック(状態やイベントハンドラなど)を結びつけます。
    4. ハイドレーションが完了すると、ページは完全にインタラクティブになります。

この仕組みにより、ユーザーはまず静的なコンテンツを即座に確認でき、その後、操作可能な UI が有効になるという、体感速度の速い体験が実現されます。

関連するディレクティブ・Hooks

'use client'と混同しやすい、あるいは関連性の高いものを整理します。

'use server'

サーバーサイドで実行される関数(Server Action)を定義するためのディレクティブです。Client Component 内からでも、このディレクティブが付与された関数を呼び出すことで、安全にサーバー側の処理(DB 更新など)を実行できます。フォームの送信処理などでよく使われます。

app/actions.js
'use server'

import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createPost(formData) {
  const title = formData.get('title');
  // ... DBに投稿を保存する処理
  await db.post.create({ data: { title } });
  revalidatePath('/posts'); // 投稿一覧ページのキャッシュを更新
}

use(Promise) (React 19)

React 19 で導入された新しい API で、Client Components において、 Promise や Context の値をより柔軟に読み取ることができます。

"use client";
import { use, useMemo } from "react";

function Profile({ userId }) {
  const userPromise = useMemo(() => fetchUser(userId), [userId]);

  const user = use(userPromise);
  return <h1>{user.name}</h1>;
}

// または、Server Component から Promise を受け取る
function Profile({ userPromise }) {
  const user = use(userPromise);
  return <h1>{user.name}</h1>;
}

実務で役立つベストプラクティス

よくあるミスとその対策

1. 過度な'use client'の使用

// × 悪い例: ページ全体に'use client'
'use client'
export default function BlogPage() {
  return (
    <div>
      <Header />
      <BlogPost />
      <LikeButton /> {/* 実際にインタラクティブなのはここだけ */}
    </div>
  )
}

// ⚪︎ 良い例: 必要な部分だけに'use client'
export default function BlogPage() { // Server Component
  return (
    <div>
      <Header />
      <BlogPost />
      <LikeButton /> {/* この中だけが Client Component */}
    </div>
  )
}

2. props の渡し方の注意点
Server Component から Client Component に渡せるのは、文字列形式に変換可能な値のみです。

// × 関数やクラスインスタンスなどは渡せない
<ClientComponent
  onClick={() => console.log('test')} // エラー
/>

// ⚪︎ プリミティブ値や plain object なら OK
<ClientComponent
  userId={123}
  title="記事タイトル"
  metadata={{ author: 'Saku', tags: ['Next.js'] }}
/>

3. デバッグのコツ
どのコンポーネントが Server/Client として動作しているかは、ブラウザの DevTools で確認できます。

  • Client Component: React DevTools で state や props が確認可能
  • Server Component: ページソースに HTML として出力済み

パフォーマンス最適化のコツ

Client Component の分割戦略

// × 大きなコンポーネントを丸ごと Client に
"use client";
function Dashboard() {
  const [data, setData] = useState(null);
  return (
    <div>
      <ExpensiveChart data={data} />
      <SimpleButton onClick={() => setData(newData)} />
    </div>
  );
}

// ⚪︎ インタラクティブな部分だけを Client に
function Dashboard() {
  // Server Component
  return (
    <div>
      <ExpensiveChart data={staticData} /> {/* Server で描画 */}
      <UpdateButton /> {/* Client Component */}
    </div>
  );
}

まとめ

今回は、Next.js App Router の'use client'ディレクティブについて、基本的な概念から実践的な使い方まで解説しました。

最初は「どこに書けばいいの?」「いつ書けばいいの?」と思うことも多いと思いますが、本稿が少しでも参考になれば幸いです。

参考資料

Discussion

Honey32Honey32

失礼します。

Server Component から Client Component に渡せるのは、文字列形式に変換可能な値のみです。

とありますが、これは誤りです。

  createdAt={new Date()} // エラー

とありますが、この createAt の受け渡しは成功します。Date オブジェクトはシリアライズ可能なので。

出典: https://ja.react.dev/reference/rsc/use-client#serializable-types


もう一点、use の解説について

use は、公式ドキュメントではすでにフックではなく「use API」と呼ばれていますが、これは脇に置きます)

注意: useフックは Client Components でも利用可能ですが、主な利点は Server Components でのデータ取得の簡素化にあると思われます

とありますが、これは逆です。use は Client Component で使うことを目的としています。

出典はド忘れしたので無いですが、Server Component 内で使うと「ここでレンダリングを中断して、コンポーネント全体のレンダリングを、またあとで再開する」と、レンダリングをかなり遅くする余計な処理が入るので推奨されていません。

下にも示した通り、「Server Component で作成した Promise を Server では await せず Client Component に渡して、Client 側では use で開封できる。」という使い方が想定されています。(上記で挙げたように、Promise は Server から Client にわたすことが可能なのを利用しています)

TanStack Query のようなライブラリがキャッシュを管理することによって、Client Component Promise を作成する使い方でも可能になるはずです。今は TanStack Query の該当機能は experimental 段階だったと思います。

サーバコンポーネントからクライアントコンポーネント に props としてプロミスを渡すことで、サーバからクライアントにデータをストリーミングすることができます。

https://ja.react.dev/reference/react/use#streaming-data-from-server-to-client

プロミスはサーバコンポーネントからクライアントコンポーネントに渡し、use API を使ってクライアントコンポーネントで解決することができます。また、await を使ってサーバコンポーネント側でプロミスを解決し、必要なデータを props としてクライアントコンポーネントに渡すことも可能でしょう。

export default async function App() {
  const messageContent = await fetchMessage();
  return <Message messageContent={messageContent} />
}

しかしサーバコンポーネントで await を使用すると、await 文が終了するまでそのレンダーがブロックされます。サーバコンポーネントからクライアントコンポーネントにプロミスを渡すことで、プロミスがサーバコンポーネントのレンダーをブロックすることを防ぐことができます。
https://ja.react.dev/reference/react/use#streaming-data-from-server-to-client

非同期コンポーネントはクライアントではサポートされていないため、プロミスの待機は use を使用して行います。

https://ja.react.dev/reference/rsc/server-components#async-components-with-server-components

sakusaku

ありがとうございます!
2点とも齟齬無いように調査し直します🙇‍♂️