「Next.jsのServer Actionsがあれば、もうAPIエンドポイントなんて一生書かなくていい」。
そう信じて疑わなかった半年前の自分を、往復ビンタしてやりたい。
個人開発のプロダクトで「爆速リリース」という麻薬に溺れ、API Routesを全廃してすべてをServer Actionsに寄せた結果、本番環境で盛大に炎上した。DBの負荷は跳ね上がり、フロントのコードは責務の境界が崩壊したスパゲッティと化した。
Server Actionsは魔法の杖じゃない。使い方を誤れば、それは「フロントエンドにバックエンドの複雑性を埋め込む時限爆弾」になる。
1. なぜServer Actionsは「地獄の入り口」になるのか
最大の過ちは、「Reactのライフサイクル」と「サーバーサイドの副作用」を安易に混ぜ合わせたことにある。
例えば、ユーザー一覧の削除ボタンにServer Actionsを直接紐付けたとしよう。ここで`revalidatePath`を呼ぶと、Next.jsはページ全体を再レンダリングしようとする。
// 悪い例:コンポーネント内にビジネスロジックを直書きする
"use server";
export async function deleteUser(id: string) {
await db.user.delete({ where: { id } });
revalidatePath("/users"); // ここでページ全体が再描画される
}
この処理が走るたび、Full Route Cacheがパージされ、サーバーは全コンポーネントの再計算を強制される。100人のユーザーを削除する処理を繰り返せば、ブラウザはカクつき、DBへのクエリはスパイクする。
なぜこうなるのか?
Next.jsのキャッシュ機構(Data Cache / Full Route Cache)を理解せず、「関数を呼べば勝手に更新してくれる」という抽象化に甘えたからだ。Server Actionsは、UIのステート更新とDBのミューテーションを「同一のコンテキスト」で実行しているように見せる。だが、裏側ではHTTPリクエストが飛び、キャッシュの無効化が走り、Reactのツリーが再構築されている。この「見えないコスト」を無視すると、アプリは確実に死ぬ。
2. 責務の分離:Service層なきServer Actionsは「悪」
Server Actionsにロジックを詰め込むと、コードは確実に腐る。特に、コンポーネントとServer Actions、さらにはDB操作が複雑に絡み合うと、循環参照(Circular Dependency)の温床になる。
推奨ディレクトリ構成:
src/
├── app/ # UIとServer Actionsの入り口
├── services/ # 【重要】純粋なビジネスロジック(DB操作)
│ └── user.service.ts
└── lib/ # 共通バリデーション(Zod)
悪い設計:
`app/users/actions.ts` の中で直接 `db.user.findMany()` を叩き、その結果をコンポーネントに渡す。
良い設計:
`services/user.service.ts` にロジックを分離し、Server Actionsは「入力の検証」と「サービス呼び出し」のただの仲介役に徹する。
// services/user.service.ts
export const deleteUserById = async (id: string) => {
// ここに複雑なトランザクションや外部API連携を隔離する
return await db.user.delete({ where: { id } });
};
// app/users/actions.ts
export async function deleteUserAction(id: string) {
// 認証チェックや権限確認もここで行う
await assertUserPermission(id);
return await deleteUserById(id);
}
この構成を徹底できないプロジェクトは、半年後に保守不能になる。「Service層を通さないDB操作はPR段階で却下する」。これくらいの厳格さがなければ、モダンなフルスタック構成は維持できない。
3. セキュリティ:API Routesを捨てる代償
API Routesを捨てると、認証やCSRF対策の責務がすべてServer Actionsに集約される。特に注意すべきは、「認証の透過性」だ。
API Routesであれば、ミドルウェアで一括ガードできたものが、Server Actionsでは一つ一つの関数にガードを書き忘れるリスクが生まれる。
- CSRF対策: Next.jsはServer Actionsに対して自動でCSRF保護をかけてくれるが、過信は禁物。
- 認証チェック: すべてのServer Actionsの先頭に `if (!session) throw new Error(...)` を書くのは現実的ではない。デコレーターパターンや、高階関数を用いたラッパーで「強制的に認証を通す」仕組みを構築すべきだ。
4. 結論:Next.jsのアーキテクチャはどうあるべきか
今後、Next.jsのアーキテクチャにおいて、Server Actionsは「万能のAPI代替」ではなく、「UIに密結合したフォーム操作の最適化レイヤー」として位置づけるのが正解だ。
- 頻繁なデータフェッチ(一覧表示など): `TanStack Query` を使い、クライアント主導のキャッシュ戦略を立てる。
- 確定的な状態更新(フォーム、DB書き込み): `Server Actions` を使い、`useActionState` でUIと同期させる。
- 複雑なドメインロジック: `Service層` に隔離し、フレームワークの境界から独立させる。
これこそが、Next.jsでフルスタック開発を生き抜くための唯一の「最適解」だ。
明日から現場で徹底すべきコードレビュー基準:
1. Service層の不在: ロジックがActionsファイルに直書きされていないか?
2. 再検証の範囲: `revalidatePath` は最小限か? 全ページをパージしていないか?
3. 認証の漏れ: 認証チェックがラッパー関数で強制されているか?
4. 型の信頼性: Zodによるバリデーションは、アクションの入り口(最初の一行)で行われているか?
技術はただのツールだ。フレームワークの便利さに溺れず、コードの構造に責任を持つ。それが、一流のエンジニアであるための最低条件だ。