Server Function の渡し方
関数は Client Component に渡せませんが、use server を書いた Server Function だけは渡せます。
#例外はひとつだけ
Server Component から Client Component へ関数は渡せません。渡すとビルドが落ちます。
// ❌ Event handlers cannot be passed to Client Component props.
<Button onClick={() => save()} />
例外は 'use server' を書いた関数、Server Function だけです。
// ✅ 渡せる
<Button onSave={save} />
#書き方は2通り
ファイルの先頭に書く。 そのファイルが export する関数すべてが対象になります。Client Component から import できるのはこの形だけです。
// app/actions.ts
'use server'
export async function submit(value: string) {
// サーバーで実行される
}
関数の先頭に書く。 その関数だけが対象になります。Server Component の中で、その場で定義するときに使います。
export default function Page() {
async function save(name: string) {
'use server'
await db.user.update({ name })
}
return <EditPost onSave={save} />
}
その場でしか使わない小さな更新なら後者が読みやすく、複数の画面から呼ぶものや、テストを書きたいものは前者に切り出します。
#「サーバーで動かす」という意味ではない
'use server' を「この処理をサーバーで動かす指示」だと読むと、置き場所を間違えます。
Server Component はもともとサーバーで動いています。書く必要はありません。
'use server' の意味は、サーバーにある関数を、クライアントから呼べるようにすることです。境界に穴を1つ開ける操作だと考えてください。
'use client' と対になっているように見えますが、役割は対称ではありません。
| 指示 | 意味 |
|---|---|
'use client' | ここから先はブラウザにも送る |
'use server' | ここをクライアントから呼べる入口にする |
#中身は届かない。呼び出しは届く
Server Function を props で渡したとき、ブラウザに届くのはどこを呼べばよいかという参照だけです。関数の中のコードは届きません。
実際に、中に特徴的な文字列を入れた Server Function を Client Component へ渡してビルドし、成果物を検査しました。HTML にも RSC ペイロードにも、配信される JavaScript にも、その文字列は含まれていませんでした。
ただし、見えないのはコードであって、通信ではありません。
開発者ツールのネットワークタブを開けば、呼び出しの引数と戻り値はそのまま観察できます。参照が分かれば、画面を経由せずに呼ぶこともできます。
だから、次の3つは関数の中で必ず行います。
- 認証と権限の確認。 公式ドキュメントも、トークンを引数で受け取らず Cookie やヘッダーから読むよう案内しています
- 引数の検証。 クライアントから来た値は、形も範囲も信用しない
- 戻り値を絞る。 データベースの行をそのまま返さない
'use server'
export async function createUser(data: { name: string }) {
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const user = await db.user.create({ data })
return { id: user.id, name: user.name } // 行をそのまま返さない
}
戻り値はシリアライズされてクライアントへ送られます。渡せる値の制限はServer から Client へ渡せる値と同じです。
#定数を同じファイルに置かない
'use server' を書いたファイルから export できるのは非同期関数だけです。
'use server'
export const CATEGORIES = ['bug', 'question'] // ❌
A "use server" file can only export async functions, found object.
これは export したものがすべて「クライアントから呼べる入口」として登録されるためで、呼べないものを置くことはできません。
定数は別のファイルに移します。このサイトを作っているときも同じ失敗をして、問い合わせ画面のビルドが落ちました。型だけの export は消えるので、type や interface は同じファイルに残して構いません。
#まとめ
- 関数は渡せない。Server Function だけが例外
'use server'は「サーバーで動かす」ではなく「クライアントから呼べるようにする」- 中身は届かないが、呼び出しは誰でもできる。認証・検証・戻り値の制限は関数の中で
- 定数は別ファイルへ
境界を越えられない関数の話はEvent handlers cannot be passedに、関数を渡さずに済ませる形はClient Component の中に Server Component を置くにあります。