【Next.js】サーバー/クライアントコンポーネントの使い分けとつまずく6つのエラーについて紹介

公開日: 2026年9月7日 · 読了目安 13 分

Next.js(App Router)のサーバーコンポーネントとクライアントコンポーネントの使い分けを、"use client" が必要な場面、初心者がつまずく6つのエラーと直し方、props と children、page.tsx や layout.tsx の役割までコード例つきで解説します。

サーバー/クライアントコンポーネントの使い分けで迷うとき

Next.js の App Router で作り始めて、ボタンに useState(状態を覚える仕組み)を書いたらエラーになった。

言われるままに "use client"(この部品はブラウザで動かす、という印)を付けたら動いたものの、どこに書けばよいのかわからない。多くの人が最初にここでつまずきます。

答えはシンプルです。

部品は何も書かずに、サーバーで組み立てる部品として作ります。状態やクリック、ブラウザの機能が必要なファイルにだけ、この1行を書きます。

しかも、なるべく末端の小さな部品に書くのがコツです。

この記事では、次のことをコード例つきで説明します。

  • サーバーで組み立てる部品と、ブラウザで動く部品の違いと分ける理由

  • ブラウザで動かす印が必要になる場面の見分け方

  • 初心者がつまずく6つのエラーと直し方

  • 部品に値を渡す方法(props と children)と、app フォルダのファイル名の役割

サーバーコンポーネントとクライアントコンポーネントの違い

コンポーネントは、画面を部品に分けた関数です。

App Router では、何も指定しなければサーバーコンポーネントとして動きます。サーバー側で実行され、できあがった HTML がブラウザに届きます。

クライアントコンポーネントは、ファイルの先頭に "use client" と書いた部品です。最初の HTML はサーバーで作られますが、その後ブラウザでも JavaScript が動き、クリックや入力に反応できるようになります。

サーバーコンポーネントに分けると得られる3つのこと

  • ブラウザに送る JavaScript が減り、ページが軽くなる

  • データベースや API からのデータ取得を、コンポーネントの中に直接書ける

  • API キーなどの秘密の値を、ブラウザに漏らさずに使える

反対に、すべてをクライアントにすると、この3つを手放すことになります。だからこそ「基本はサーバー、必要なところだけクライアント」という分け方が勧められています。

クライアントコンポーネント(use client)が必要な場面

次のどれかを使うファイルには、ブラウザで動かす印が必要です。どれも使わないなら、何も書かなくてかまいません。

  • 部品の状態や処理のタイミングを扱う React のフック(useState、useEffect、useRef など)

  • クリックや入力に反応するイベントの処理

  • ブラウザだけにある機能

  • Motion(旧 Framer Motion)のように、ブラウザで動くことが前提のライブラリ

イベントの処理の例は、onClick(クリックしたとき)や onChange(入力が変わったとき)です。

ブラウザだけにある機能の例は、window(画面やタブの情報)や localStorage(ブラウザに値を保存する場所)です。

書く場所はファイルの1行目です。import より前に置いてください。

components/Counter.tsx
"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

サーバーとクライアントのコンポーネントを組み合わせるルール

サーバーで組み立てる部品から、ブラウザで動く部品を import することはできます。逆に、ブラウザで動く部品のファイルが読み込んだ部品は、すべてブラウザ側で動きます。

ただし、children(中に入れる要素)や props(部品に渡す値)として渡された部品は例外です。

サーバーで組み立てる部品を、中に入れる要素としてブラウザ側の部品に渡すと、そのままサーバーで描画されます。この仕組みは、後で説明するエラー3の直し方でも使います。

サーバー/クライアントコンポーネントでつまずく6つのエラー

サーバーとクライアントの使い分けで出るエラーは、ほとんどが次の6つのどれかです。エラーの文面は Next.js のバージョンで少し変わるので、内容で見分けてください。

エラー1:useState や useEffect が使えない

「このフックはクライアントコンポーネントでしか動かない」という趣旨のエラーです。そのファイルの先頭に、ブラウザで動かす印を書けば直ります。

ページのファイル(page.tsx)で起きた場合は、フックを使う部分だけを別ファイルに切り出します。そのうえで、この1行を付けましょう。

エラー2:クリック時の関数を親からブラウザ側の部品に渡せない

サーバーで組み立てる部品の中で関数を作り、それを props としてブラウザ側の部品に渡したとします。

すると「イベントハンドラーはクライアントコンポーネントに渡せない」という趣旨のエラーになります。

サーバーで作った普通の関数は、ブラウザへ送れないためです。"use server" を付けたサーバーアクションは例外です。

直し方は、クリック時の処理をブラウザで動く部品の中に移すことです。親から渡すのは、文字列や数値などの値だけにします。

components/LikeButton.tsx
"use client";

import { useState } from "react";

// 処理はクライアント側の中に書く。親からは値だけ受け取る
export function LikeButton({ initial }: { initial: number }) {
  const [likes, setLikes] = useState(initial);
  return <button onClick={() => setLikes(likes + 1)}>♥ {likes}</button>;
}

エラー3:layout.tsx で Context が使えない

テーマやログイン状態を共有しようとして、layout.tsx で createContext を使うとエラーになります。Context はクライアントの機能だからです。

Provider(値を配る部品)だけをブラウザで動く部品に切り出し、layout.tsx から children を包む形にします。

中に入れる要素として渡したページは、サーバーで組み立てる部品のままです。

app/providers.tsx
"use client";

import { createContext, useState, type ReactNode } from "react";

export const ThemeContext = createContext<"light" | "dark">("light");

export function Providers({ children }: { children: ReactNode }) {
  const [theme] = useState<"light" | "dark">("light");
  return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}

// app/layout.tsx 側
// <body><Providers>{children}</Providers></body>

エラー4:環境変数がブラウザで読めない

クライアントコンポーネントで process.env.API_KEY を読んでも、値が入りません(undefined や空文字になります)。

ブラウザに届くのは、名前が NEXT_PUBLIC_ で始まる環境変数だけだからです。

秘密のキーを使う処理は、サーバーで組み立てる部品に移します。または、app/api/ フォルダに置くサーバー側の処理(Route Handler)に移してください。

名前に NEXT_PUBLIC_ を付けて解決しようとすると、キーが誰にでも見える状態になります。

エラー5:サーバーとブラウザで表示が食い違う

Hydration(サーバーで作った HTML にブラウザの JavaScript をつなぐ処理)のエラーです。

描画の中で、現在時刻(new Date())や乱数(Math.random())を使うとします。するとサーバーとブラウザで結果が変わり、この食い違いが起きます。

ブラウザの情報を持つ window を描画の中で読んだときも、同じことが起きます。

ブラウザでしか決まらない値は、useEffect(表示のあとに処理を走らせる仕組み)の中で取得して、状態に入れます。最初の描画ではサーバーと同じ表示になるので、食い違いが出ません。

components/Clock.tsx
"use client";

import { useEffect, useState } from "react";

export function Clock() {
  const [now, setNow] = useState<string | null>(null);
  useEffect(() => {
    setNow(new Date().toLocaleTimeString());
  }, []);
  return <p>{now ?? "--:--:--"}</p>;
}

エラー6:"use client" を上の階層に書いてページが重くなる

エラーの表示は出ませんが、いちばん多い失敗です。

エラー1を消すために、ページ全体のファイル(page.tsx や layout.tsx)にこの1行を書いたとします。

すると、そこから読み込んだ部品がすべてブラウザ側で動きます。データ取得もサーバーでできなくなります。

Motion でヒーローを動かす場合も同じです。ページ全体ではなく、動く部品のファイルだけにブラウザで動かす印を書き、ページからそれを読み込みます。

components/FadeIn.tsx
"use client";

import { motion } from "motion/react";
import type { ReactNode } from "react";

export function FadeIn({ children }: { children: ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 16 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true }}
      transition={{ duration: 0.6 }}
    >
      {children}
    </motion.div>
  );
}

// app/page.tsx(サーバーのまま)
// <FadeIn><h2>機能の紹介</h2></FadeIn>

サーバーコンポーネントでも使える props と children

部品に値を渡す方法は、props と children の2つです。どちらも、サーバー側とブラウザ側の両方の部品で使えます。

props:ページごとに変わる値を渡す

ヘッダーのタイトルのように、ページごとに変わる値は props で受け取ります。受け取った値は読み取り専用で、受け取った側で書き換えてはいけません。

components/SiteHeader.tsx
type SiteHeaderProps = { title: string };

export function SiteHeader({ title }: SiteHeaderProps) {
  return (
    <header className="border-b px-4 py-3">
      <h1 className="text-lg font-bold">{title}</h1>
    </header>
  );
}

// 使う側
// <SiteHeader title="DesignLayer" />

children:タグの中身をそのまま受け取る

カードのように、枠は共通で中身だけを変えたい部品には children を使います。タグの間に書いた要素が、そのまま中身として渡されます。

components/Card.tsx
import type { ReactNode } from "react";

export function Card({ children }: { children: ReactNode }) {
  return <div className="rounded-lg border p-4 shadow-sm">{children}</div>;
}

// 使う側:中身は自由
// <Card>
//   <h2>料金プラン</h2>
//   <p>月額1,000円から</p>
// </Card>

ファイル名で決まる役割とクライアントコンポーネント

App Router では、決められたファイル名に役割があります。よく使うものは次のとおりです。

  • page.tsx:その URL の画面

  • layout.tsx:配下のページで共有する枠。ページを移動しても作り直されない

  • loading.tsx:データ取得中に自動で表示される画面。読み込み中の仮の表示を置く場所

エラーのときや、ページを移動するたびの表示もファイル名で決まります。

  • error.tsx:エラーが起きたときの画面。ブラウザで動かす印が必須

  • not-found.tsx:404 の画面

  • template.tsx:共有の枠に似ているが、移動のたびに作り直される。入場アニメーションを毎回再生したいときに使う

このうち、忘れやすいのが error.tsx に書くブラウザで動かす印です。

error.tsx はエラーを受け止める仕組み(エラー境界)として、ブラウザ側で動きます。再試行のボタンがなくても、ブラウザで動く部品にする必要があります。

app/error.tsx
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>再試行</button>
    </div>
  );
}

取得・表示・操作でサーバーとクライアントコンポーネントを分ける

1つの部品にデータ取得や表示、ボタンの処理をすべて入れると、あとから直しにくくなります。役割ごとに3つに分けると、サーバーとクライアントの境目も自然に決まります。

  • 取得:サーバーで組み立てる部品(page.tsx など)で、async 関数として書く

  • 表示:渡された値を受け取って描画するだけの部品。ブラウザで動かす印は不要

  • 操作:クリックや入力を扱う小さな部品だけを、ブラウザで動く部品にする

app/posts/page.tsx
import { LikeButton } from "@/components/LikeButton";

type Post = { id: string; title: string; likes: number };

// 取得:サーバーコンポーネント
export default async function PostsPage() {
  const res = await fetch("https://example.com/api/posts");
  const posts: Post[] = await res.json();
  return <PostList posts={posts} />;
}

// 表示:props を受け取るだけ
function PostList({ posts }: { posts: Post[] }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          {post.title}
          {/* 操作:ここだけクライアント */}
          <LikeButton initial={post.likes} />
        </li>
      ))}
    </ul>
  );
}

動く部品はクライアントコンポーネントに切り出す

ボタンやフォームのような汎用の部品は、毎回ゼロから書く必要はありません。よく使われる shadcn/ui は、コンポーネントのソースを自分のリポジトリにコピーする方式なので、あとから自由に書き換えられます。

bash
npx shadcn@latest add button

アニメーション付きのクライアントコンポーネントを使う場合

入場アニメーションやスクロール連動のセクションは、自分で書くと1つに半日かかることもあります。

DesignLayer では、動きのある React セクションをブラウザで動かして確かめ、TSX + Tailwind CSS のソースをコピーできます。

ブラウザで動かす印や、必要なパッケージの一覧も含まれています。

そのため、エラー6のようにページ全体をブラウザ側にしてしまう心配がありません。https://design-layer.com/ja/components

Motion を自分で書く場合の基本は、別の記事で解説しています。https://design-layer.com/ja/articles/nextjs-framer-motion-guide

サーバー/クライアントコンポーネントについてよくある質問

  • 最初の HTML はサーバーで作られます。そのうえでブラウザでも JavaScript が動き、操作に反応できるようになります。

    "use client" は「ブラウザでも動く部品」という印と考えるとわかりやすいです。

まとめ:Next.js のサーバー/クライアントコンポーネントの使い分け

  • 部品は何も書かずにサーバー側で作り、状態・クリック・ブラウザの機能が要るファイルにだけ "use client" を書く

  • この1行はなるべく末端の小さな部品に書き、ページ全体や共有の枠には書かない

  • クリック時の関数はサーバーから渡さない。秘密の値はサーバーで使う。ブラウザでしか決まらない値は表示のあとに取る

  • 中に入れる要素として渡せば、ブラウザ側の部品の中でもサーバーのまま描画できる

  • app フォルダではファイル名が役割を決める

基本を押さえたら、実際に LP を組んでみるのが早道です。

Cursor を使って1日で作る手順は次の記事にまとめています。https://design-layer.com/ja/articles/cursor-nextjs-landing-page-guide

ヒーローに動きを付ける方法は、こちらで解説しています。https://design-layer.com/ja/articles/nextjs-animated-hero-section-guide

関連ガイド

【Next.js】サーバー/クライアントコンポーネントの使い分けとつまずく6つのエラーについて紹介 | DesignLayer