本文へ移動
verified onnext@16.3.3react@19
最終検証日 2026-08-27
Next.js道場

Client Component の中に Server Component を置く

Client Component の中に Server Component を置きたいときは、import ではなく children で渡します。

#import と children は別のこと

「Client Component の中で Server Component を使いたい」と思ったとき、書き方は2つあります。片方は動き、片方はビルドで落ちます。

// ❌ Modal.tsx の中で import する
'use client'

import { Cart } from './Cart'   // Cart がクライアント側の依存になる

export function Modal() {
  return <div><Cart /></div>
}
// ✅ Server Component 側で作って、children で渡す
export default function Page() {
  return (
    <Modal>
      <Cart />
    </Modal>
  )
}

違いは「Cart のコードが、どのファイルから import されているか」だけです。

  • 上は Client Component のファイルから import している → Cart もクライアント側の依存になる
  • 下は Server Component のファイルから import している → Cart はサーバー側に残る

置かれる位置ではなく、import した位置で決まります。

#渡っているのは、描いたあとの結果

下の書き方で Modal が受け取るのは、Cart というコンポーネントではありません。サーバーですでに描き終えた結果です。

Next.js の公式ドキュメントも、この形では Server Component が先にサーバーで描画され、RSC ペイロードにはその結果と、Client Component を置く位置の目印が入ると説明しています。

つまり Modal から見ると、children は「もう出来上がっている中身」です。中で何が起きたかは知りません。データベースを読んでいても、API キーを使っていても、Modal には関係ありません。

この性質のおかげで、

  • 開閉やタブ切り替えの状態 → ブラウザ
  • 中身のデータ取得 → サーバー

を、同じ画面の中で分けられます。

#エラーは、引き込んだ側ではなく引き込まれた側に出る

うっかり import してしまったときのエラーは、少し読みにくい形で出ます。

./components/Cart.tsx:1:1
Error: You're importing a module that depends on "next/headers".
This API is only available in Server Components in the App Router,
but you are using it in the Pages Router.

指されているのは Cart.tsx の1行目です。しかし直すべきなのは Modal.tsx の import です。Cart は悪くありません。

さらに、末尾に「but you are using it in the Pages Router」と出ます。App Router で書いていてもこの文が出ます。Pages Router を使っているわけではないので、ここは無視してください。

犯人を見つけるには、エラーの下に出る Import traces下から上へ読みます。

Import traces:
  Client Component Browser:
    ./components/Cart.tsx      ← 巻き込まれた
    ./components/Modal.tsx     ← ここに use client がある
    ./app/page.tsx

use client があるファイルが、境界の入口です。

#Provider も同じ形

React の Context は Server Component では使えません。だからテーマや認証状態を配るには、Provider を Client Component にする必要があります。

// app/theme-provider.tsx
'use client'

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}
// app/layout.tsx  ← Server Component のまま
export default function RootLayout({ children }) {
  return (
    <html lang="ja">
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

Provider は Client Component ですが、その中の children は Server Component のままです。layout も Server Component のままで構いません。

公式ドキュメントは、Provider を htmlbody ではなく children のできるだけ近くに置くことを勧めています。包む範囲が広いほど、静的に扱える部分が減るからです。

#外部のライブラリを使うとき

useState を使っているのに 'use client' が書かれていないパッケージがあります。そのまま Server Component から使うとエラーになります。

このときも解決の形は同じで、自分のファイルで包みます。

// components/Carousel.tsx
'use client'

export { Carousel } from 'acme-carousel'

これで、そのパッケージは自分の管理下にある境界の内側に入ります。

#まとめ

やりたいこと書き方
Client の中に Server を置くchildren や props で渡す
Context を配るProvider だけを Client にして children を包む
use client の無い外部部品を使う自分のファイルで再 export して包む
Client の中で import するできない。ビルドで落ちる

判断に迷ったら、Server と Client の選び方も合わせて読んでください。境界を越える props の制限はServer から Client へ渡せる値にまとめてあります。

理解できたか試す

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

Modal は Client Component です。その children に置いた Cart は、どこで実行されますか。

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