"use server"; でexportした関数が意図せず?公開される
Next.js AppRouterで利用できるReactのServer Actions機能。クライアントからサーバ上の処理を関数で呼び出せるので非常に便利ですが、 "use server";
のことをあまり知らず、誤った使い方をすると意図せず公開したくない関数が外部に公開されてしまうケースがあるので注意です(ほとんどこんなケースはないと思いますが、なくはないので注意喚起です)。
Server Actionsの例
Server Actions用の関数として宣言するためには "use server";
が必要です。それ以外は至って普通の非同期関数で大丈夫です。
"use server";
export async function someAction() {
return {
message: "Server Action",
};
}
次に定義したServer Actionsの関数を呼び出すクライアントコンポーネントの例です。ボタンをクリックすると、上記の関数をサーバ上で実行し {message: "someAction"}
というレスポンスを返します。
"use client";
import { someAction } from "@/app/someAction";
export function CallButton() {
return (
<button
type="button"
onClick={async () => {
const result = await someAction();
console.log(result);
}}
>
Call
</button>
);
}
実装コードは非常にシンプルですね。
Server Actionsはどう実現されているのか
クライアントからサーバ上の処理を関数で呼び出せる…というのは、実態としては fetch
関数でPOSTリクエストをしているだけです。具体的なリクエストをcurlで再現すると以下のようになっています。
curl 'http://localhost:3000/' \
-X POST \
-H 'Content-Type: text/plain;charset=UTF-8' \
-H 'Next-Action: c067dbb1c3ea2bce4b8204d09befc20f9b40ace4' \
--data-raw '[]'
ポイントはリクエストヘッダの Next-Action
です。この値は、Server Actionsの関数を特定するためのIDです。
また、レスポンスは定義した関数の返り値が専用のフォーマットで返ってきます。
0:["$@1",["development",null]]
1:{"message":"Server Action"}
つまりServer Actionsとは言ってしまえば単なる普通の公開APIです。
公開するつもりのない関数がさらされるケース
こういうコードを書くことはほとんどないと思うのですが、度重なるコード修正をしていると混ざってしまうかもしれません。以下のようなServer Actionsは危険です。
"use server";
export async function someAction() {
const result = await privateFunc();
return {
message: result,
};
}
// プライベート用途の関数だが…
export async function privateFunc() {
return "private function";
}
privateFunc()
関数の定義を見ると export
されてしまっています。これは別のファイルで再利用したいという意図かもしれないし、ケアレスミスかもしれないですが、非常に危険です。
なぜなら "use server";
が付いたファイルで export
された関数はすべて外部から呼び出すことが出来るEndpointとして生成されてしまうから です。これはつまり privateFunc()
を誰でも呼び出すこと出来るということです。
最初に紹介したcurlの Next-Action
ヘッダで利用するIDさえ分かってしまえばリクエストができてしまいます。そしてそのIDは普通にブラウザ側のJavaScriptファイルに出力されているため簡単に判明します。
スクリプトにServer ActionsのIDが記載されいてる様子
まとめ
"use server";
が付いたファイルで export
するということは、外部から呼び出せるということ。Server Actionsの仕組みを理解してコードを書いていきましょう。
Discussion
有益な情報ありがとうございます!
こんにちは!
質問があります!
例えばプライベート関数でもテストを書く際にはテスト用のファイルから実装を呼び出すためには、関数に
export
をつける必要があると思います。しかし実際にデプロイされるときにはそのままでは
export
がついているので外部に露出してしまうのですね。外部に露出させたくはないが、別ファイルでテストを書きたいときはどのようにすればいいでしょう。
(vitestのin-source testingは一つの解答になりうるのですが、やはりテストの実装はファイルを分けることが多いと思うのでこの質問をさせていただきました)
private functionは
'use server'
のついていない別のファイルに記載するのが正解でしょうか。質問ありがとうございます。結論から言うと
export
したいなら別ファイルに切り出したほうが安全です。Next.js v15からはソースコード上にServer Actions関数を特定するIDが不用意に露出しないようになっているので、おおきく問題にはならないかもしれませんが外部からリクエストされうる状況は変わりません。つまり、外部から呼び出されると困るような関数はおっしゃるように
"use server";
のないファイルに分離するのが安全です。語弊がある言い方かもしれませんが
"use server";
は、いわゆるWeb MVCフレームワークで言うコントローラ(ルーティング機能付き)に該当し、外部からのリクエストを受け取って、何らかのレスポンスを返す機能です。詳細なビジネスロジックは別ファイル(MVCのM)に切り出す…みたいなイメージだと切り分けしやすいかもしれませんね。了解です、ありがとうございます。
「
"use server"
を使ってexportした時点でもうそれはAPI Endpoint」これだけ覚えて帰ろうと思います!