使用Server Actions+TanStack Query更新字段时遇PrismaClient浏览器错误
Error: PrismaClient is unable to run in this browser environment, or has been bundled for the browser (running in
unknown). If this is unexpected, please open an issue:
at Object.get (index-browser.js:203:15)
at getDataById (get-all-apointment.ts:55:27)
at approvedStatus (update-status.ts:11:45)
at Object.mutationFn (admin-column.tsx:140:62)
at Object.fn (mutation.js:72:29)
at run (retryer.js:92:31)
at Object.start (retryer.js:133:9)
at Mutation.execute (mutation.js:107:40)
我尝试用 Server Actions 结合 TanStack Query 更新数据库单个字段,但点击“批准按钮”时触发上述错误。相关代码如下:
列定义代码
export type Events = { id: string; title: string; email: string; fullName: string; contactPerson: string; department: string; status: string; dateOfEvent: string; startingTime: string; endingTime: string; purpose: string; doesHaveDryRun: boolean; dryRunDate?: string | null; // 可选 dryRunStart?: string | null; // 可选 dryRunEnd?: string | null; // 可选 doesHaveTCETAssitance: string; tcetOtherAssitance?: string | null; // 可选 meetingTypeOption: string; meetingTypeServices: string; meetingTypeServiceLink?: string | null; // 可选 cameraSetup?: string | null; // 可选 }; export const columns: ColumnDef<Events>[] = [ /* 其他列 */ { id: "actions", cell: ({ row }) => { const data = row.original; const router = useRouter(); // 获取路由实例 const { mutate: approvedButton } = useMutation({ mutationFn: async (id:string) => await approvedStatus(id), onError: (error) => { console.log(error); }, onSuccess: () => { router.push("/admin/calendar"); router.refresh(); }, }); return ( <DropdownMenu> <DropdownMenuTrigger asChild> <Button variant="ghost" className="h-8 w-8 p-0"> <span className="sr-only">打开菜单</span> <MoreHorizontal className="h-4 w-4" /> </Button> </DropdownMenuTrigger> <DropdownMenuContent align="end"> <DropdownMenuLabel>操作</DropdownMenuLabel> <DropdownMenuItem onClick={() => router.push(`/admin/edit/${data.id}`)} > <PanelRightCloseIcon className="mr-2 h-4 w-4" /> 查看详情 </DropdownMenuItem> <DropdownMenuSub> <DropdownMenuSubTrigger> <UserPlus className="mr-2 h-4 w-4" /> <span>更新状态</span> </DropdownMenuSubTrigger> <DropdownMenuPortal> <DropdownMenuSubContent> <DropdownMenuItem onClick={() => approvedButton(data.id)}> <Check className="mr-2 h-4 w-4 text-emerald-600" /> <span>已批准</span> </DropdownMenuItem> <DropdownMenuItem> <CircleDot className="mr-2 h-4 w-4 text-rose-600" /> <span>待处理</span> </DropdownMenuItem> <DropdownMenuSeparator /> </DropdownMenuSubContent> </DropdownMenuPortal> </DropdownMenuSub> <DropdownMenuSeparator /> <DropdownMenuItem> <Trash2 className="mr-2 h-4 w-4" /> 删除 </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ); }, }, ];
Server Actions 代码
export const approvedStatus = async (id:string) => { try { const isExisting = await getDataById(id) if(!isExisting) return {error: "数据不存在"} await db.appoinmentSchedule.update({ where: { id: id }, data: { status: 'approved' } }) return {success: "事件状态更新成功!"} } catch (error) { console.log(error) throw error } }
这个错误的核心原因是你的 Server Action 被打包到浏览器端执行了,而 PrismaClient 只能在 Node.js 服务器环境运行,无法在浏览器中调用。
1. 标记 Server Action
在 Next.js 中,必须用 'use server' 指令明确标记 Server Action,确保它只在服务器端执行。修改你的 approvedStatus 函数:
'use server' // 必须放在文件最顶部 export const approvedStatus = async (id:string) => { try { const isExisting = await getDataById(id) if(!isExisting) return {error: "数据不存在"} await db.appoinmentSchedule.update({ where: { id: id }, data: { status: 'approved' } }) return {success: "事件状态更新成功!"} } catch (error) { console.log(error) throw error } }
2. 检查关联函数
getDataById 函数如果也使用了 PrismaClient,同样需要标记 'use server',确保它也是服务器端执行的逻辑,不会被客户端打包。
3. 确认代码目录规范
建议将 Server Action 放在 app/actions/ 目录下,Next.js 会自动识别该目录下的文件为服务器端代码,避免被客户端打包。
4. 保持客户端调用逻辑正确
你当前用 TanStack Query 的 mutationFn 调用 Server Action 的写法是正确的,只要确保 approvedStatus 被正确标记为服务器端函数,Next.js 会自动处理客户端到服务器的请求转发。
内容的提问来源于stack exchange,提问作者Stykgwar

