为何tRPC服务器无法接收上传的图片?
解决方案:Next.js + tRPC + React-Hook-Form 头像上传后端获取空对象问题
核心原因
tRPC 默认使用 JSON 序列化请求体,但 File 对象无法被 JSON 序列化,导致前端传入的 File 到后端被转成空对象。必须改用 FormData 提交文件类数据。
步骤1:前端表单提交改为 FormData 格式
修改 React-Hook-Form 的提交逻辑,手动构建 FormData 而非依赖默认的 JSON 序列化:
import { useForm } from 'react-hook-form'; import { updateProfile } from '@/app/_trpc/client'; const ProfileForm = () => { const form = useForm({ defaultValues: { name: '', profilePicture: undefined, }, }); const onSubmit = form.handleSubmit(async (data) => { const formData = new FormData(); // 添加普通表单字段 formData.append('name', data.name); // 添加头像文件(确保data.profilePicture是单个File对象而非FileList) if (data.profilePicture) { formData.append('profilePicture', data.profilePicture); } // 调用tRPC mutation await updateProfile.mutateAsync(formData); }); return ( <form onSubmit={onSubmit}> {/* 普通输入字段 */} <FormField control={form.control} name="name" render={({ field }) => ( <input type="text" {...field} placeholder="用户名" /> )} /> {/* 头像输入字段,手动处理onChange确保传入单个File对象 */} <FormField control={form.control} name="profilePicture" render={({ field }) => ( <input type="file" accept="image/*" onChange={(e) => field.onChange(e.target.files?.[0])} /> )} /> <button type="submit">提交</button> </form> ); };
步骤2:后端 tRPC Mutation 支持 FormData 解析
在 tRPC 路由中,跳过默认的 JSON 解析,手动从请求中获取 FormData,同时用 Zod 做校验:
1. 确保 Context 包含 Request 对象
// app/_trpc/trpc.ts import { initTRPC } from '@trpc/server'; import { type NextRequest } from 'next/server'; export const createTRPCContext = async (opts: { req: NextRequest }) => { return { req: opts.req }; }; const t = initTRPC.context<typeof createTRPCContext>().create(); export const publicProcedure = t.procedure; export const router = t.router;
2. 编写支持 FormData 的 Mutation
// app/_trpc/router/profile.ts import { z } from 'zod'; import { publicProcedure, router } from '../trpc'; export const profileRouter = router({ update: publicProcedure .input(z.void()) // 跳过自动解析,手动从context获取FormData .mutation(async ({ ctx }) => { const formData = await ctx.req.formData(); // 解析并校验字段 const name = formData.get('name') as string; const profilePicture = formData.get('profilePicture') as File | null; // 校验逻辑 if (!name) throw new Error('用户名不能为空'); if (profilePicture && profilePicture.size > 5 * 1024 * 1024) { throw new Error('头像大小不能超过5MB'); } // 处理文件(比如上传到存储服务、保存到本地等) // 示例:读取文件buffer const buffer = await profilePicture?.arrayBuffer(); return { success: true, message: '资料更新成功' }; }), });
关键注意事项
- 不要依赖 tRPC 自动解析 File 类型:JSON 无法序列化 File,必须用 FormData 传递
- React-Hook-Form 的文件输入要手动处理
onChange:将e.target.files[0]传给field.onChange,避免传入无法序列化的 FileList - 后端必须从 Request 对象中手动提取 FormData:tRPC 默认的输入解析只支持 JSON 格式
内容的提问来源于stack exchange,提问作者Henrique
相关产品推荐
相关产品推荐

