You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:00:54