API Routeを捨ててServer Actionsへ。Next.js開発を「脱・冗長」する最適解
「APIエンドポイントを作って、型定義を共有して、`fetch`を叩く」。
この儀式、正直もう飽き飽きしてないか?
かつての僕もそうだった。フロントとバックエンドを律儀に分離し、エンドポイント名に悩み、`schema.ts`を両方のディレクトリにコピーしては同期ズレに泣く。そんな「化石のような開発」を続けていたせいで、どれほどの時間を浪費したことか。
Next.jsのApp Routerが登場して久しいが、未だにこの古いパラダイムから抜け出せないエンジニアが多い。Server Actionsは単なる「便利な機能」じゃない。フロントとバックエンドの境界線を消滅させ、開発体験を根本から覆すための武器だ。
今回は、現場で泥臭くコードを書き、何度もキャッシュの罠にハマり、徹夜でデバッグした末に見つけた「Server Actionsで勝ち抜くための設計論」を叩き込む。
1. なぜ「API Route」は現代の足枷になるのか
API Routeが非効率な理由は明確だ。「型定義の二重管理コスト」と「ネットワークオーバーヘッド」だ。
- 型管理の地獄: APIのレスポンス型をフロントで定義し直す。バックエンドの変更がフロントに伝播せず、型安全が崩壊する。
- オーバーヘッド: クライアントからわざわざHTTPリクエストを飛ばし、サーバー側でパースし、レスポンスをJSON化して返す。これ、内部関数呼び出しならミリ秒で終わる話だ。
// app/actions/post.ts
'use server'
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title') as string
// バリデーションをサボるな。ここが守りの要だ
if (!title) return { error: 'タイトルは必須だ。適当にやるなよ。' }
try {
await db.post.create({ data: { title } })
revalidatePath('/posts')
return { success: true }
} catch (e) {
return { error: 'サーバーで何か起きた。ログを見ろ。' }
}
}
2. 認証統合でハマる「キャッシュの闇」
Auth.js (NextAuth) を使っているなら要注意だ。Server Actions内で`auth()`を呼ぶと、往々にして「ログアウトしたのにセッション情報が残る」という現象に遭遇する。
原因はNext.jsのData Cacheだ。Server Actionsも内部的にはキャッシュの対象になり得るため、セッション情報が古いまま固定されることがある。
解決策:明示的な無効化と、実行タイミングの制御
import { auth } from '@/auth'
import { revalidatePath } from 'next/cache'
export async function deletePost(id: string) {
const session = await auth()
if (!session) throw new Error('権限なし')
await db.post.delete({ where: { id } })
// 削除後はキャッシュをクリアし、最新の状態を強制取得させる
revalidatePath('/posts')
}
この「キャッシュをどう制御するか」という感覚こそ、今のNext.js開発における最大のスキルセットだ。ここを理解せず「なんとなく動く」で済ませていると、半年後に大規模なセキュリティホールや整合性エラーとして自分に跳ね返ってくる。
3. 型安全の幻想をぶち壊す「バリデーション」
「Server Actionsは型安全だから安心」なんて言っている奴は危うい。クライアントから送られてくる`FormData`は、依然として「型のないただの箱」だ。
Zodによる型変換の強制こそが、メンテ可能なコードへの唯一の道だ。
import { z } from 'zod'
const PostSchema = z.object({
title: z.string().min(1).max(100),
})
export async function createPost(prevState: any, formData: FormData) {
const result = PostSchema.safeParse(Object.fromEntries(formData))
if (!result.success) {
return { error: result.error.flatten().fieldErrors }
}
// バリデーション通過後は、result.dataで型安全に突き進める
const { title } = result.data
// ...DB操作
}
4. まとめに:使い分ける判断基準
Server Actionsは最強だが、万能ではない。以下の表を指針にしてくれ。
| シナリオ | 推奨アプローチ | 理由 |
|---|---|---|
| フォーム送信・データ更新 | Server Actions | 状態の再検証(revalidate)が容易で、UXが向上する。 |
| 高頻度なデータ取得 | Server Components | キャッシュ制御が最も洗練されており、最速。 |
| 外部からのAPIリクエスト | Route Handlers | Webhook受け取りなど、クライアント以外との通信用。 |
| 複雑なロジックの分離 | tRPC / 外部API | フロントエンドとサーバーが完全に独立したチーム体制なら。 |
最後に
技術は道具だ。流行っているから使うのではなく、「なぜこれを使うのか」を自分の言葉で説明できるようになれ。Server Actionsを使いこなすことは、単にコードを短くすることじゃない。「複雑さをどこに閉じ込めるか」というアーキテクチャの主導権を、自分の中に取り戻すことだ。
さあ、古いAPI Routeの設計書はゴミ箱に捨てて、もっと泥臭く、もっとモダンなコードを書こうぜ。半年後の自分に感謝されるような、強固なコードを。