UI for Admin — コーディングルール
バリデーション
真実の源
バックエンド Zod 4 スキーマが唯一の真実の源。UI 側の Zod は以下の経路で生成される:
これにより、管理者 API のリクエストスキーマが変わればフェッチャー型とフォーム Zod の両方が同時に更新される。
OpenAPI 経由で保持される制約
- プリミティブ型(string, number, boolean, integer)
required/optionalenum値min/max、minLength/maxLength- 文字列フォーマット(
email,uuid,date-time, ...) - 正規表現(
pattern) - ネストしたオブジェクト形状
OpenAPI 経由で 保持されない 制約
.refine()/.superRefine()(クロスフィールド検証、条件付きルール).transform()による出力形状変換- ブランド型
- バックエンドでオーサリングされたカスタムエラーメッセージ
これらは UI 側で補う必要がある。
ハンドオーサリング refinement
src/api/validators/<resource>.ts に生成 Zod をラップした派生スキーマを配置する:
// src/api/validators/admin.ts
import { z } from 'zod'
import { adminCreateBodySchema as generated } from '@/api/generated/admin.zod'
export const adminCreateBodySchema = generated.refine(
(v) => v.password === v.confirmPassword,
{
path: ['confirmPassword'],
message: 'validation.password.confirm.mismatch', // next-intl のキー
},
)
規約:
- ファイル名はリソース単位(
admin.ts,organization.ts, ...) - エクスポート名は生成スキーマと同じで可。「UI 用に拡張されたもの」である点はファイルロケーションで示す
- バックエンドが同じ refinement を追加したときは、本ファイル側の refinement を削除できる。Orval 生成物のみで済む場合は常にそちらを優先
React Hook Form との統合
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { adminCreateBodySchema } from '@/api/validators/admin'
const form = useForm<z.infer<typeof adminCreateBodySchema>>({
resolver: zodResolver(adminCreateBodySchema),
defaultValues: { /* ... */ },
})
エラーメッセージと i18n
- エラーメッセージの文字列値は i18n キー にする(日本語・英語の文字列を Zod に直接書かない)
- レンダリング時に
next-intlのt(field.error.message)でローカライズ - バックエンドから返る 400 エラーは
error.issuesをフォーム上のsetError()で対応するフィールドに割り当てる
クライアント側バリデーションのスコープ
- UX のみ — レイテンシ削減と即時フィードバックのため
- セキュリティ境界ではない — 管理者 API が同じ Zod スキーマで再検証する。サーバーが権威
- スキップしない — 生成済み Zod を投入するだけでコストはほぼゼロ
API クライアント利用規約
TanStack Query との統合
Orval が生成するフックは TanStack Query v5 ベース。カスタマイズは queryOptions 引数で行う:
const { data, isLoading } = useGetAdmins(
{ page, limit, sort },
{ query: { staleTime: 30_000, placeholderData: keepPreviousData } },
)
ミューテーションの成功後は queryClient.invalidateQueries({ queryKey: getGetAdminsQueryKey() }) でリストを再取得。
エラーハンドリング
- 401 → フェッチャーのリフレッシュ機構に委譲(自動リトライ、詳細は セットアップ)
- 403 → 権限不足。TanStack Query の
onErrorでトーストを出し、必要に応じて/loginにリダイレクト - 5xx → サーキットブレイカー的挙動は入れない(管理者 API 側にレート制限あり)。ユーザーに再試行を促す UI を提示
生成ファイルの扱い
生成されたファイル(src/api/generated/)は コミットする。人間が手動編集した場合、次の generate:api で上書きされる。追加のロジックは src/api/validators/ か UI 側で記述する。