概要
Next.js(App Router)と Rails API を組み合わせた構成でWebアプリを作成した場合、
- フロントエンドとバックエンド両方でバリデーションしたい
- 最終的な正しさはサーバーで保証したい
- バリデーションロジックの二重管理は避けたい
と考えることが普通かと思います。
そこで、ここでは、
- Zod を用いた Next.js 側のバリデーション
- Rails 側で担保すべき責務
- どこまでをフロントで、どこからをバックエンドで行うべきか
を、実例ベースで整理しようと思います。
想定構成
- Frontend: Next.js (App Router)
- Validation: Zod + react-hook-form
- Backend: Rails (API Mode)
フロントエンド(Next.js)でのバリデーション責務
フロントエンドのバリデーションは「UX向上」が目的として考えられます。
-
入力ミスを即座にユーザーへ伝える
-
無駄な API リクエストを減らす
-
UI側と密結合な制約を扱う
Zod を使ったスキーマ定義例
import { z } from "zod";
export const contactSchema = z.object({
name: z
.string()
.min(1, "名前が入力されていません。")
.max(16, "最大16文字までです。"),
email: z
.string()
.min(1, "メールアドレスが入力されていません。")
.email("メールアドレスの形式で入力してください。"),
subject: z
.string()
.min(1, "件名が入力されていません。")
.max(50, "最大50文字までです。"),
message: z
.string()
.min(1, "本文が入力されていません。")
.max(200, "最大200文字までです。"),
});
export type ContactFormValues = z.infer<typeof contactSchema>;
フロントで行うべきバリデーションの例
| 種類 | 例 |
|---|---|
| 必須チェック | 空文字、未入力 |
| 形式チェック | email, URL |
| 文字数制限 | max/min |
| UI依存 | 利用規約チェック、確認チェック |
これらは Zod + react-hook-form で即時フィードバックするのが最適です。
Rails(バックエンド)でのバリデーション責務
Rails 側は「信頼できない入力」を前提にします。
- フロントのバリデーションは 必ず突破される
- API は curl / Postman / 改ざんリクエストを想定する
- DB整合性・業務ルールはすべてサーバーで担保する
Rails 側で必須なバリデーション例
class Contact < ApplicationRecord
validates :name, presence: true, length: { maximum: 16 }
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :subject, presence: true, length: { maximum: 50 }
validates :message, presence: true, length: { maximum: 200 }
end
Rails でしかできないバリデーション
| 種類 | 例 | |
|---|---|---|
| DB依存 | unique制約 | |
| 業務ルール | 同一メールの送信回数制限 | |
| 認可 | ログインユーザーのみ送信可 | |
| 状態依存 | ステータスによる制限 |
これらは フロントに置いてはいけません。
## 二重管理への考え方
- 結論:二重管理は「避けられない」
TS / Ruby 間でのコード共有は現実的ではありません。よってFEではUX改善を、BEではデータの正当性を保証するというう目的で、制約を分割して考えるべきです。
バリデーション目的がそもそも違う
実務でのおすすめ分割
コードをコピーする
Zod(Next.js)
├─ 必須
├─ 形式
├─ 文字数
└─ UX向上用
Rails
├─ 必須
├─ 形式
├─ 文字数
├─ 業務ロジック
├─ 認可
└─ DB制約
エラーハンドリングの流れ
-
Zod で即時エラー表示
-
OKなら API 送信
-
Rails で最終検証
-
Rails のエラーを UI に反映
まとめ
FEとBEのバリデーション責務に関しては、ZodはUX のため
Railsは正しさのために分割を行うことがよいと考えています。
このように役割を分ければ、二十に管理している部分があったとしても、保守性はむしろ上がります。
ここでの、Next.js + Rails 構成では、それぞれの役割を分割することで、より適切なバリデーション分割を考えました。