Next.js Server Actions:APIルートの「捨て時」と、実務で直面する現実的な壁

Next.js Server Actions:APIルートの「捨て時」と、実務で直面する現実的な壁

「APIルートを書いて、JSONを叩いて、フロントで型を同期させる」。この一連の儀式に、どれだけの時間を溶かしてきただろうか。

Next.jsのApp Routerが登場して久しいが、未だに「バックエンドとフロントエンドを分離すべき」という教科書的な設計に縛られ、本来不要なボイラープレートを量産している現場をよく見かける。Server Actionsは単なる便利機能ではない。開発の「手触り」を根本から変えるパラダイムシフトだ。

しかし、実務で使い込むほどに、単なる「APIの置き換え」ではない、特有の難しさが顔を出す。今回は、Server Actionsを本番環境で運用して見えてきた「理想」と「現実」、そして避けて通れない設計の最適解を共有する。


1. なぜ「型定義の同期」をあきらめるべきなのか

APIルートを分離すると、フロントとバックの間に「型」という名の境界線が生まれる。これがOpenAPIやtRPCの導入を強要し、結果としてコードベースを複雑にする。

Server Actionsの強みは、サーバーサイドの関数をクライアントから直接呼び出せることにある。Zodを組み合わせるだけで、バリデーションと型安全が単一のファイルで完結する。

実戦的なフォーム実装:react-hook-formとの連携

`Object.fromEntries(formData)` はシンプルだが、ネストされたオブジェクトや配列の扱いには脆い。実務では `react-hook-form` と組み合わせるのが定石だ。

// actions/user.ts
'use server'

import { z } from 'zod'

const schema = z.object({
  name: z.string().min(2),
  tags: z.array(z.string()).optional(),
})

export async function updateUser(prevState: any, data: z.infer<typeof schema>) {
  const result = schema.safeParse(data)
  if (!result.success) return { success: false, error: result.error.flatten() }

  // Prisma等でのDB操作
  await db.user.update({ ... })
  
  // キャッシュの再検証(重要!)
  revalidatePath('/profile')
  return { success: true }
}

ここで重要なのは、「何が成功したか」をフロントにどう伝えるかだ。Server ActionsはJSONシリアライズの制約があるため、Dateオブジェクトをそのまま返すことはできない。`ISO文字列` に変換するか、プリミティブな値に限定してやり取りするのが、この世界での「作法」だ。


2. Server Actionsが突きつける「キャッシュ」の闇

Server Actionsを導入して真っ先にぶつかる壁が、データの一貫性(Cache Invalidation)だ。

Next.jsは強力なキャッシュ機構を持っているが、Server ActionsでDBを更新した際、フロント側のキャッシュが古いまま残っているとUIが更新されない。`revalidatePath` や `revalidateTag` を適切に配置しないと、ユーザーは「ボタンを押したのに何も変わらない」という体験をすることになる。

楽観的更新(useOptimistic)の活用

ユーザー体験を損なわないためには、サーバーのレスポンスを待たずにUIを更新する「楽観的更新」が不可欠だ。

// components/user-form.tsx
'use client'

import { useOptimistic } from 'react'

export function UserProfile({ initialName }) {
  const [optimisticName, setOptimisticName] = useOptimistic(initialName)

  const action = async (formData: FormData) => {
    setOptimisticName(formData.get('name')) // 即座にUI反映
    await updateUser(formData) // サーバー処理
  }

  return <form action={action}>{optimisticName}</form>
}


3. 「APIルートを切り出すべき」境界線はどこか

「Server Actionsは疎結合を壊す」という批判はあるが、それは「何でもかんでもServer Actionsに詰め込む」から起きる問題だ。

アーキテクチャ推奨されるケース
Server Actionsフォーム送信、UIに直結する更新処理、個人開発、小〜中規模プロダクト
API Route / Honoモバイルアプリとの共通バックエンド、外部連携(Webhook等)、複雑な認証フロー

判断基準の目安:
1. 「その処理を他のクライアント(スマホアプリ等)から呼ぶか?」:YesならAPIルートへ。
2. 「複雑なキャッシュ制御が必要か?」:YesならAPIルートで制御を分離する。
3. 「開発スピードがビジネスの最優先事項か?」:Yesなら迷わずServer Actions。


結論:技術は「使い手」を選ぶ

Server Actionsは強力なハンマーだが、使い所を間違えれば自分の指を叩くことになる。

キャッシュの再検証を忘れ、楽観的更新を実装せず、ただ「APIを書かなくて済む」という理由だけで採用するのは早計だ。しかし、この密結合な開発体験に慣れてしまうと、もはやAPIルートを介した開発には戻れない。

「疎結合」という言葉に逃げて、不必要な抽象化に時間を溶かすのはもう終わりにしよう。Next.jsというフレームワークの恩恵を最大限に受けるなら、その密結合こそが、今の時代における最速の武器になる。

次は、お前がそのコードで何を作るかだ。