← 中文资源

简体中文版本

Next Server Action 输入校验

在 Next.js 服务端动作里用 Zod 校验表单数据再执行变更:状态机式返回值驱动渐进增强表单,禁用 JS 也可用。

阅读英文原文

示例说明

Server Action 让表单提交直达服务端函数,绕过了前端校验层——服务端校验因此从最佳实践升级为唯一防线。FormData 的值全是字符串,z.coerce 承接数字转换;返回值用状态机联合类型表达空态、错误与成功,配合 useActionState 渐进增强:无 JS 环境原生表单提交照常工作。

技术要点

  • Server Action 入口即校验边界
  • z.coerce 处理 FormData 字符串转型
  • revalidatePath 让提交后页面数据刷新

代码解读

submitFeedback 接收前一个状态与原始表单数据,safeParse 失败时把 issues 展平成字段错误映射返回 error 态;成功路径生成工单号并触发路径重验证。三态返回类型约束了客户端渲染的全部可能分支,漏处理某态会编译报错。

使用建议

评分等受限输入用下拉或单选避免自由文本。错误态保留用户已输入内容提升体验。Action 内的数据库写入要带幂等保护,双击提交不应重复建档。

代码示例

import { z } from 'zod';
import { revalidatePath } from 'next/cache';

const FeedbackSchema = z.object({
  email: z.string().email(),
  rating: z.coerce.number().int().min(1).max(5),
  message: z.string().min(10).max(1000),
});

export type FeedbackState =
  | { status: 'idle' }
  | { status: 'error'; fields: Record<string, string> }
  | { status: 'success'; ticket: string };

export async function submitFeedback(
  _prev: FeedbackState,
  formData: FormData,
): Promise<FeedbackState> {
  const parsed = FeedbackSchema.safeParse({
    email: formData.get('email'),
    rating: formData.get('rating'),
    message: formData.get('message'),
  });

  if (!parsed.success) {
    const fields: Record<string, string> = {};
    for (const issue of parsed.error.issues) {
      fields[issue.path.join('.')] ??= issue.message;
    }
    return { status: 'error', fields };
  }

  const ticket = 'FB-' + Date.now().toString(36);
  revalidatePath('/feedback');
  return { status: 'success', ticket };
}