「API Routeを全廃してServer Actions一本に絞る」。半年前にそう決断した自分を、今の俺なら間違いなくぶん殴りに行くだろう。
フロントエンドとバックエンドの境界が溶ける体験は、確かに甘美だった。だが、個人開発のプロジェクトをフルリファクタリングした先に待っていたのは、Next.jsの「Router Cache」という名の深い沼だった。深夜3時、更新ボタンを押しても画面が変わらず、DBのデータだけが書き換わっているログを眺めていた時のあの虚無感。あれは、魔法に酔いしれた代償としてはあまりに高すぎた。
夢を見た「魔法の杖」の正体
Server Actionsは、単なるAPIの代替品ではない。Reactのライフサイクルと密接に結合した「副作用の塊」だ。
// 悪夢の始まり:再検証の罠
export async function updateProfile(data: ProfileData) {
await db.user.update({ where: { id: userId }, data })
revalidatePath('/profile')
}
このコード、何が問題か。`revalidatePath`を叩けばサーバー上のデータは確かに最新になる。だが、クライアント側の「Router Cache」は別腹だ。Next.jsの仕様上、ページ遷移やアクション実行後、クライアント側のキャッシュが破棄されるタイミングは、開発者が思っている以上に気まぐれだ。
`useOptimistic`でUIを先読みさせようものなら、さらに地獄が加速する。サーバーからのレスポンスと、クライアントの楽観的UI更新が競合し、画面がパカパカと点滅する。結局、`router.refresh()`を叩き込むという、API Route時代と何ら変わらない「力技」に頼ることになった。公式ドキュメントに小さく書かれた「Router Cache」の仕様を読み返した時、自分がどれだけ薄氷の上を歩いていたかを思い知った。
「型安全」という名の負債
Zodでバリデーションを共通化すれば幸せになれる。そう信じていた時期が俺にもあった。
// 散らかり放題だった当時のコード
export async function updateProfile(prevState: any, formData: FormData) {
const result = profileSchema.safeParse(Object.fromEntries(formData))
if (!result.success) {
return { error: '入力内容が不正です' } // どのフィールドがダメなのかUIに伝わらない
}
}
これの何が悲劇かというと、フロントエンド側で`useFormState`を駆使してエラーを拾う際、フィールド単位のバリデーションメッセージをUIにマッピングするコードが爆発的に増えることだ。API Route時代は、axiosのインターセプターでエラーを共通処理できていた。Server Actionsに移行した途端、その「共通化の知見」をすべて自前で再実装させられる羽目になる。
結局、今は以下のように「レスポンスの型」を完全に固定した共通レイヤーを噛ませることで、ようやくコードの腐敗を食い止めている。
// 現実的な落とし所:戻り値の型を統一する
type ActionResponse<T> = {
success: boolean;
errors?: Record<keyof T, string>;
message?: string;
};
// これを各Actionで継承して使うようにした
泥臭い「現在地」
結局のところ、Server Actionsは「書くのが楽」なだけで、「設計のコスト」はAPI Route時代より遥かに高い。API Routeという防波堤をなくすことは、Next.jsという巨大なフレームワークの内部挙動を、自分自身で制御下に置くという宣言に他ならない。
今の俺は、こう割り切っている。
1. キャッシュは「パス」ではなく「タグ」で管理する。 `revalidatePath`は乱用せず、`revalidateTag`で特定のデータだけに絞る。
2. UIの同期は、`useTransition`でローディングを明示し、`router.refresh()`は「同期用」ではなく「強制再読み込み用」の最終手段と心得る。
3. バリデーションはサーバーとクライアントで分離する。 DRY原則を無理に守って結合度を上げるより、多少の重複は許容して堅牢性を取る。
Server Actionsは、型安全という名の麻薬を報酬に、開発者の設計能力を試してくる。もしあなたが「API Routeを削除してスッキリさせたい」という理由だけでServer Actionsへ移行しようとしているなら、一度立ち止まってほしい。
そのコード、本当にデバッグする準備ができているか?
俺は今、Server Actionsの便利さと、API Routeの堅牢性の間で、泥臭くバランスを取りながらコードを書いている。洗練されたモダンなアーキテクチャなんて存在しない。あるのは、自分が触れる範囲で制御しきれるかどうかの「現実」だけだ。
さて、今夜もキャッシュの挙動と睨めっこする時間だ。この沼にハマったことが、果たして正解だったのか。答えが出るのは、もう少し先の話になりそうだ。