本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
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 の価値です。

付けていなくても、Client Component から fs のようなサーバー専用のモジュールにたどり着くと、Can't resolve 'fs' でビルドが止まります。ただし原因の場所が分かりにくい文言です(Can't resolve 'fs' の直し方)。

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

client-only は、その逆

server-only と client-only の使い分け
付ける対象server-onlyclient-only
DB・API キーに触る層付ける付けない
window / localStorage に触る層付けない付ける
どちらからも使う純粋な関数付けない付けない
止まる場所ビルドビルド

window や localStorage に触るモジュールには 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.5 は server-only と client-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 問