Next.js中tRPC客户端Mutation触发'createUnhandledError'错误求助
排查Next.js + tRPC项目中createProject Mutation的createUnhandledError错误
错误现象
实现创建项目的Mutation时,浏览器控制台抛出createUnhandledError错误,错误栈如下:
Error Stack Tracetypescript[[ << mutation #1 ]project.createProject {}Call StackcreateUnhandledErrorfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_next_dist_client_9bb038..js (744:19)handleClientErrorfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_next_dist_client_9bb038..js (905:98)errorfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_next_dist_client_9bb038..js (1036:56)[project]/node_modules/@trpc/client/dist/links/loggerLink.mjs [app-client] (ecmascript)/defaultLogger/<file:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_2661d4..js (1250:15)logResultfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_2661d4..js (1286:31)errorfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_2661d4..js (1299:34)errorfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_@trpc_server_dist_072945..js (283:30)errorfile:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules@trpc_server_dist_072945..js (51:30)[project]/node_modules/@trpc/client/dist/links/httpBatchStreamLink.mjs [app-client] (ecmascript)/unstable_httpBatchStreamLink/</</</<file:/D:/PROJECTS/lunaris/.next/static/chunks/node_modules_2661d4..js (990:34)
客户端代码
"use client"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { api } from "@/trpc/react"; import React from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; type FormInput = { repoUrl: string; projectName: string; githubToken?: string; }; const CreatePage = () => { const { register, handleSubmit, reset } = useForm<FormInput>(); const createProject = api.project.createProject.useMutation(); function onSubmit(data: FormInput) { createProject.mutate( { githubUrl: data.repoUrl, name: data.projectName, githubToken: data.githubToken, }, { onSuccess: () => { toast.success("Project Created Successfully"); reset(); }, onError: (error) => { toast.error("Failed to create project"); console.error("Mutation Error:", error); }, } ); return true; } return ( <div className="flex h-full items-center justify-center gap-12"> <img src="/undraw_github.svg" className="h-56 w-auto" /> <div> <div> <h1 className="text-2xl font-semibold">Link your Github Repository</h1> <p className="text-sm text-muted-foreground"> Enter the URL of your repository to link it to Lunaris! </p> </div> <div className="h-4"></div> <div> <form onSubmit={handleSubmit(onSubmit)}> <Input {...register("projectName", { required: true })} placeholder="Project Name" required /> <div className="h-2"></div> <Input {...register("repoUrl", { required: true })} placeholder="Github Url" type="url" required /> <div className="h-2"></div> <Input {...register("githubToken")} placeholder="Github Token" /> <div className="h-4"></div> <Button type="submit">Create Project</Button> </form> </div> </div> </div> ); }; export default CreatePage;
服务器端配置
Prisma Schema
generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt imageURL String? firstName String? lastName String? emailAddress String @unique credits Int @default(150) userToProjects UserToProject[] } model Project{ id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt name String githubUrl String deletedAt DateTime? userToProjects UserToProject[] } model UserToProject{ id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String projectId String user User @relation(fields: [userId], references: [id]) project Project @relation(fields: [projectId], references: [id]) @@unique([userId, projectId]) }
tRPC Router
import { z } from "zod"; import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc"; export const projectRouter = createTRPCRouter({ createProject: protectedProcedure.input( z.object({ name: z.string(), githubUrl: z.string(), githubToken: z.string().optional() }) ).mutation(async({ctx, input})=>{ // const project =await ctx.db.project.create({ // data:{ // githubUrl: input.githubUrl, // name: input.name, // userToProjects:{ // create:{ // userId: ctx.user.userId!, // } // } // } // }) console.log("input",input) return true }) })
复现步骤
- 克隆仓库:
git clone https://github.com/arghyabag/lunaris.git cd lunaris - 使用Bun安装依赖:
bun install - 配置环境变量:在根目录创建
.env文件,添加以下变量:DATABASE_URL=your_postgres_connection_string NEXTAUTH_SECRET=your_secret_key - 执行Prisma迁移:
npx prisma migrate dev - 启动开发服务器:
bun dev - 访问应用:http://localhost:3000
部署步骤
- 全局安装Bun:
curl -fsSL https://bun.sh/install | bash - 构建生产版本:
bun build - 运行生产构建:
bun start
排查思路与解决方法
1. 阻止表单默认提交行为
客户端onSubmit函数返回true会触发浏览器默认表单提交,导致页面刷新、tRPC请求中断,进而引发未处理错误。修改方式:
function onSubmit(data: FormInput) { createProject.mutate( { githubUrl: data.repoUrl, name: data.projectName, githubToken: data.githubToken, }, { onSuccess: () => { toast.success("Project Created Successfully"); reset(); }, onError: (error) => { toast.error("Failed to create project"); console.error("Mutation Error:", error); }, } ); // 移除return true,或改为return false阻止默认行为 return false; }
也可以通过接收表单事件手动阻止:
<form onSubmit={(e) => handleSubmit((data) => onSubmit(data, e))(e)}>
function onSubmit(data: FormInput, e: React.FormEvent) { e.preventDefault(); // 剩余逻辑不变 }
2. 确认认证状态与用户信息
createProject使用protectedProcedure要求用户必须登录,若未登录提交表单会触发服务器端认证错误,若错误未被正确传递会引发未处理异常:
- 给CreatePage添加登录校验,未登录用户无法访问该页面
- 检查
tRPC上下文配置(trpc.ts中的createTRPCContext),确保ctx.user.userId从NextAuth Session中正确提取:export const createTRPCContext = async (opts: { req: NextRequest; res: NextResponse }) => { const session = await getServerSession(opts.req, opts.res, authOptions); return { db: prisma, user: session?.user, }; }; - 若NextAuth返回的用户信息无
userId,需在authOptions的session回调中补充:session: { strategy: "jwt", callbacks: { session({ session, token }) { if (token.sub) { session.user.userId = token.sub; } return session; }, }, },
3. 处理服务器端潜在错误
即使注释了数据库操作,也要确保服务器端无未捕获错误:
import { TRPCError } from "@trpc/server"; // ... 其他代码 .mutation(async({ctx, input})=>{ try { console.log("input",input) // 启用数据库操作前,校验用户ID存在 if (!ctx.user?.userId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "用户未登录" }); } // const project =await ctx.db.project.create({...}) return true } catch (err) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: "创建项目失败", cause: err }); } })
4. 检查tRPC客户端链路配置
确认tRPC客户端链路(如httpBatchStreamLink、loggerLink)配置正确,避免链路中存在未处理的错误逻辑导致异常。
内容的提问来源于stack exchange,提问作者Arghya Bag
相关产品推荐
相关产品推荐

