できない.dev

Next.js App Router で useState が使えないエラーが解消できない

App Router は既定でサーバーコンポーネント。useState などのフックや onClick を使うファイルは先頭に use client ディレクティブが必要。
書き忘れ・位置ミス・境界設計の不一致が主因。

公開: 更新:

実行例あり(2026-09-14 に実環境で検証)

要約

App Router でフックが使えないエラーが出る場合、原因はほぼ「use client ディレクティブを書いていない / 位置が誤っている」。
サーバーコンポーネントが既定で、フックや onClick などのイベントハンドラはクライアント側でしか動かない。

実行例

"use client" を書いていない Counter.js を page.js から読み込むと、next build は useState がクライアントコンポーネントでしか動かないというエラーで終了コード 1 になり、ディレクティブを import の下に置いた場合も The "use client" directive must be placed before other expressions が加わって同じく失敗した。
ファイル先頭へ移すとビルドが通り、/ は静的ページとして生成されている。

$ npx next build
▲ Next.js 15.5.25
 
   Creating an optimized production build ...
Failed to compile.
 
./app/Counter.js
Error:   x You're importing a component that needs `useState`. This React Hook only works in a Client Component. To fix, mark the file (or its parent) with the `"use client"` directive.
  |
  |  Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
  |
 
   ,-[/tmp/tmp.mTiXS4AI3F/app/Counter.js:1:1]
 1 | import { useState } from "react";
   :          ^^^^^^^^
 2 | 
 3 | export function Counter() {
 4 |   const [n, setN] = useState(0);
   `----
 
Import trace for requested module:
./app/Counter.js
./app/page.js
 
> Build failed because of webpack errors
$ echo $?
1
$ npx next build
▲ Next.js 15.5.25
 
   Creating an optimized production build ...
Failed to compile.
 
./app/Counter.js
Error:   x The "use client" directive must be placed before other expressions. Move it to the top of the file to resolve this issue.
   ,-[/tmp/tmp.mTiXS4AI3F/app/Counter.js:2:1]
 1 | import { useState } from "react";
 2 | "use client";
   : ^^^^^^^^^^^^^
 3 | 
 4 | export function Counter() {
 5 |   const [n, setN] = useState(0);
   `----
  x You're importing a component that needs `useState`. This React Hook only works in a Client Component. To fix, mark the file (or its parent) with the `"use client"` directive.
  |
  |  Learn more: https://nextjs.org/docs/app/api-reference/directives/use-client
  |
 
   ,-[/tmp/tmp.mTiXS4AI3F/app/Counter.js:1:1]
 1 | import { useState } from "react";
   :          ^^^^^^^^
 2 | "use client";
 3 | 
 4 | export function Counter() {
   `----
 
Import trace for requested module:
./app/Counter.js
./app/page.js
 
> Build failed because of webpack errors
$ echo $?
1
$ npx next build
▲ Next.js 15.5.25
 
   Creating an optimized production build ...
 ✓ Compiled successfully in 1976ms
   Linting and checking validity of types ...
   Collecting page data ...
   Generating static pages (0/4) ...
   Generating static pages (1/4) 
   Generating static pages (2/4) 
   Generating static pages (3/4) 
 ✓ Generating static pages (4/4)
   Finalizing page optimization ...
   Collecting build traces ...
 
Route (app)                                 Size  First Load JS
┌ ○ /                                      272 B         103 kB
└ ○ /_not-found                            991 B         103 kB
+ First Load JS shared by all             102 kB
  ├ chunks/255-37e0f0325134c4d7.js       46.4 kB
  ├ chunks/4bd1b696-c023c6e3521b1417.js  54.2 kB
  └ other shared chunks (total)          1.88 kB
 
○  (Static)  prerendered as static content
$ echo $?
0

— 2026-09-14 時点の出力

検証環境

検証日
実行環境
node:20 Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. use client 書き忘れ: 「You're importing a component that needs useState. It only works in a Client Component.」エラーが出る。
    Next.js 15.5 では文言が「You're importing a component that needs useState. This React Hook only works in a Client Component. To fix, mark the file (or its parent) with the "use client" directive.」に変わっているが、原因と直し方は同じである
  2. ディレクティブの位置が誤り: import の下に書くと無効になり、ビルドエラーになる
  3. 境界設計のミス: 親をクライアント化して、本来サーバーで実行したい子コンポーネントまで巻き込んでいる
  4. クライアント専用ライブラリ: window などブラウザ API に依存する SDK をサーバー側で評価して落ちる

解決策

1. ディレクティブを正しく書く

公式の Client Components の解説(新しいタブで開く) のとおり、ファイル先頭に書く。

"use client";
 
import { useState } from "react";
 
export function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

2. 境界を細かく分ける

page.tsx 自体はサーバーコンポーネントのままにし、インタラクション部分のみ別ファイル(例: Counter.tsx)に切り出して "use client"; を付ける。
データ取得はサーバー・クライアントの使い分け(新しいタブで開く) に従いサーバー側で完結させると配信量も減る。

3. クライアント専用ライブラリを動的読み込み

import dynamic from "next/dynamic";
 
const Editor = dynamic(() => import("./Editor"), { ssr: false });

ブラウザ API に依存するライブラリはこれでサーバー側評価を回避できる。use client を付けたファイルから動的 import するのが定石。

この記事は役立ちましたか?