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

server-only と client-only の使い方

モジュールを間違った側から使わせないための印が server-only と client-only です。

#共有したモジュールは、どちらからでも呼べてしまう

Server Component と Client Component は、同じ lib/ の中のモジュールを使えます。便利ですが、危険でもあります。

// lib/env.ts
export function getAdminKey() {
  return process.env.ADMIN_KEY
}

このファイルには「サーバー専用です」とはどこにも書いてありません。誰かが Client Component から呼んでも、エディタは何も言いません。

#「NEXT_PUBLIC_ が無ければ安全」は間違い

いちばん多い誤解がこれです。実際に確かめました。

上の getAdminKey を Client Component から呼び、ADMIN_KEY=s3cr3t... を与えて本番相当のビルドを行い、成果物を検査した結果です。

成果物秘密の値
配信される JavaScript含まれない
プリレンダーされた HTML含まれる

NEXT_PUBLIC_ が決めているのは「その環境変数をブラウザ向けの JavaScript に埋め込むかどうか」だけです。

一方 Client Component は、初回表示のためにサーバーでも一度描画されます。そのときサーバーには本物の値があります。だから描画結果に混ざり、HTML として配信されます。

つまり、

  • 仕組みが守っているのはバンドル
  • 守っていないのは描画結果

です。画面に出していなくても、属性に入れただけでも、HTML には残ります。

#server-only を付けると、ビルドで止まる

// lib/env.ts
import 'server-only'

export function getAdminKey() {
  return process.env.ADMIN_KEY
}

この1行を足すと、Client Component から呼んだときにビルドが失敗します。

./lib/env.ts:1:1
Error: 'server-only' cannot be imported from a Client Component module
It should only be used from a Server Component.

止まる場所が「公開したあと」から「ビルド」まで上がります。これが server-only の価値です。

判定は import の依存グラフで行われます。直接呼んでいなくても、Client Component からたどり着ける位置にあれば止まります。

#client-only は、その逆

windowlocalStorage に触るモジュールには client-only を付けます。

// lib/viewport.ts
import 'client-only'

export function widthOf() {
  return window.innerWidth
}

Server Component から呼ぶと、こうなります。

./lib/viewport.ts:1:1
Error: 'client-only' cannot be imported from a Server Component module
It should only be used from a Client Component.

付けていない場合は「window is not defined」という実行時エラーになります。実行時のエラーは、条件によっては本番でだけ、画面のある部分でだけ起きます。ビルドで止められるなら、そのほうが確実です。

詳しくはwindow is not definedを読んでください。

#npm から入れなくても動く

Next.js 16.3.3 は server-onlyclient-only の解決を本体側で行います。インストールしなくても動きます。

公式ドキュメントも、インストールは任意だと明記しています。依存関係の記述を厳しく検査する設定を使っている場合だけ、入れておくとよい、という位置づけです。

pnpm add server-only

#どこに付けるか

モジュール付ける印
データベースに触るserver-only
API キー・トークンを読むserver-only
認証・権限の判定server-only
window・localStorage に触るclient-only
DOM の寸法を測るclient-only
純粋な計算・整形付けない(どちらからも使ってよい)

迷ったら「これがブラウザに届いて困るか」で決めてください。困るなら server-only です。

props としてブラウザへ渡ってしまう値の話はブラウザに何が届くかに、渡せる値の制限はServer から Client へ渡せる値にまとめてあります。

理解できたか試す

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

getAdminKey は NEXT_PUBLIC_ の付かない環境変数を読みます。この Panel を本番ビルドすると、ADMIN_KEY の値はどこに出ますか。

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