NextJS+TRPC文件上传报错求助:404与格式匹配错误
解决NextJS + TRPC 文件上传的404与格式匹配错误
问题场景
使用NextJS结合TRPC开发Web应用,在标注"use client"的页面中有文件选择表单,希望通过TRPC将文件传递到后端处理,但调用时出现两个错误:
- 404 Not Found(uploadFile接口)
- TRPCClientError: The string did not match the expected pattern
用户提供的代码
客户端代码
"use client" import * as z from "zod" import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "../ui/form" import { Button } from "../ui/button" import { Input } from "../ui/input" import { trpc } from "@/app/_trpc/client" const TrainForm = () => { const formSchema = z.object({ file: z.instanceof(File) }) const { mutate: uploadFile, isLoading } = trpc.uploadFile.useMutation() const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema) }) function onSubmit(data: z.infer<typeof formSchema>) { console.log(data.file) // Handle file uploadFile({ file: data.file }) } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className=" space-y-4 p-8"> <FormField control={form.control} name="file" render={({ field }) => ( <Input accept="application/pdf" type="file" name="file" onChange={(e: any) => { const file = e.target.files?.[0] field.onChange(file) }} /> )} /> <Button disabled={isLoading} type="submit"> Submit </Button> </form> </Form> ); } export default TrainForm;
TRPC路由代码
import { privateProcedure, publicProcedure, router } from './trpc'; import { TRPCError } from '@trpc/server' import { db } from '@/db'; import { z } from 'zod'; export const appRouter = router({ // query to get data // mutation to modify data // Private uploadFile: publicProcedure.input(z.object({ file: z.instanceof(File) })).mutation(async ({input}) => { // handle file }), }); // Export type router type signature, // NOT the router itself. export type AppRouter = typeof appRouter;
错误信息
[Error] Failed to load resource: the server responded with a status of 404 (Not Found) (uploadFile, line 0) [Error] TRPCClientError: The string did not match the expected pattern. (anonymous function) (app-index.js:32) (anonymous function) (hydration-error-info.js:41) (anonymous function) (mutation.mjs:167)
问题根源
- 404错误:TRPC的API路由配置缺失或路径不匹配,导致客户端无法找到对应的后端接口。
- 格式匹配错误:TRPC默认使用JSON传输数据,但浏览器的
File对象无法被JSON序列化;同时后端Node环境中不存在浏览器的File类,z.instanceof(File)的验证在后端完全无效。
解决方案
步骤1:修复TRPC API路由配置
确保在app/api/trpc/[trpc]/route.ts文件中正确配置TRPC的请求处理器,文件内容如下:
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'; import { appRouter } from '@/server/trpc/router'; // 路径根据你的项目结构调整 const handler = (req: Request) => fetchRequestHandler({ endpoint: '/api/trpc', req, router: appRouter, createContext: () => ({}), // 若无上下文需求,返回空对象即可 }); export { handler as GET, handler as POST };
步骤2:调整客户端文件传输方式
TRPC无法直接通过JSON传输File对象,需改用FormData包装文件。修改客户端的onSubmit函数:
function onSubmit(data: z.infer<typeof formSchema>) { const formData = new FormData(); formData.append('file', data.file); uploadFile(formData); }
步骤3:修正TRPC后端的输入验证
后端Node环境无法识别浏览器的File类,需改用FormData解析并验证文件。更新TRPC路由代码:
import { privateProcedure, publicProcedure, router } from './trpc'; import { TRPCError } from '@trpc/server'; import { db } from '@/db'; import { z } from 'zod'; import { parseFormData } from '@trpc/server/adapters/fetch'; export const appRouter = router({ uploadFile: publicProcedure // 手动解析并验证FormData .input(async (opts) => { const formData = await parseFormData(opts.req); const file = formData.get('file'); if (!file || typeof file === 'string') { throw new TRPCError({ code: 'BAD_REQUEST', message: '请上传有效的PDF文件' }); } // 可选:验证文件类型 if (!file.type.includes('application/pdf')) { throw new TRPCError({ code: 'BAD_REQUEST', message: '仅支持PDF格式文件' }); } return { file }; }) .mutation(async ({ input }) => { // 这里可以处理文件,比如读取为Buffer、上传到存储服务等 console.log('收到文件:', input.file.name, input.file.size); return { success: true, filename: input.file.name }; }), }); export type AppRouter = typeof appRouter;
步骤4:确认客户端TRPC配置路径
确保客户端TRPC客户端的请求URL与API路由一致,比如在app/_trpc/client.ts中:
import { createTRPCReact } from '@trpc/react-query'; import type { AppRouter } from '@/server/trpc/router'; import { httpBatchLink } from '@trpc/client'; export const trpc = createTRPCReact<AppRouter>(); export const createTRPCClient = () => { return trpc.createClient({ links: [ httpBatchLink({ url: '/api/trpc', // 必须与API路由的endpoint一致 }), ], }); };
验证
完成上述修改后,重新启动项目:
- 选择PDF文件并提交,控制台应打印文件信息,无404错误
- 后端可以正常接收并处理文件
内容的提问来源于stack exchange,提问作者Santiago Padilla
相关产品推荐
相关产品推荐

