Next.js 13项目使用Prisma获取数据报错:无法在浏览器运行
解决Next.js中PrismaClient无法在浏览器运行的问题
核心问题
你直接在前端组件(Dashboard)里引入了包含PrismaClient的代码,而Prisma是仅能在服务端运行的库,不能被打包到浏览器环境中,所以触发了报错。正确的做法是通过Next.js的API路由作为中间层,在服务端处理Prisma数据库操作,前端通过HTTP请求获取数据。
具体修复步骤
1. 整理Prisma工具文件
把你的connect-db.ts放在lib目录(服务端工具代码的常规存放位置),保持原代码不变:
// lib/connect-db.ts import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); export async function getAllIssues() { const allIssues = await prisma.issue.findMany({ }); return allIssues; } export async function getProjects() { const projects = await prisma.projects.findMany({ }); return projects; } export async function getAllData() { const allIssues = await getAllIssues(); const projects = await getProjects(); return { allIssues, projects, }; }
2. 创建API路由封装数据库操作
在pages/api目录下新建dashboard-data.ts文件,这里的代码会在服务端运行,安全调用Prisma:
// pages/api/dashboard-data.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { getAllData } from '../../lib/connect-db'; import { getServerSession } from 'next-auth/next'; import { authOptions } from './auth'; // 假设你的next-auth配置文件在这里 export default async function handler( req: NextApiRequest, res: NextApiResponse ) { // 可选:验证用户登录状态,确保只有授权用户能访问数据 const session = await getServerSession(req, res, authOptions); if (!session) { return res.status(401).json({ error: 'Unauthorized' }); } try { const data = await getAllData(); res.status(200).json(data); } catch (error) { res.status(500).json({ error: 'Failed to fetch data' }); } }
3. 修改Dashboard组件,通过API获取数据
把前端组件里直接调用Prisma相关函数的代码,改成通过fetch请求上面的API路由,同时修正变量名不匹配的问题:
// pages/dashboard.tsx import React, { useState, useEffect } from "react"; import { useSession } from "next-auth/react"; import Layout from "../components/layout"; import AccessDenied from "../components/access-denied"; interface Issue { id: string; url: string; title: string; identifier: string; createdAt: string; } export default function Dashboard() { const [myName, setMyName] = useState(""); const [allIssues, setAllIssues] = useState<Issue[]>([]); const [pagination, setPagination] = useState({ first: 20, after: null }); const [projects, setProjects] = useState<any[]>([]); const { data: session } = useSession(); useEffect(() => { async function getIssues() { try { // 调用服务端API路由获取数据 const response = await fetch('/api/dashboard-data'); if (!response.ok) throw new Error('Request failed'); const data = await response.json(); setAllIssues(data.allIssues); setProjects(data.projects); setMyName(session?.user?.name || ''); } catch (error) { console.error("Error retrieving data:", error); } } if (session) getIssues(); }, [session]); if (!session) { return ( <Layout> <AccessDenied /> </Layout> ); } const createNewTask = () => { window.location.href = ""; }; const loadMoreIssues = async () => { const lastIssue = allIssues[allIssues.length - 1]; const after = lastIssue ? lastIssue.id : null; setPagination((prevPagination) => ({ ...prevPagination, after })); // 可选:实现分页加载,需要修改API路由接收after参数并调整Prisma查询 // const response = await fetch(`/api/dashboard-data?after=${after}`); // const moreData = await response.json(); // setAllIssues(prev => [...prev, ...moreData.allIssues]); }; return ( <Layout> <div className=""> <h1 className="text-6xl py-5">Hi, {myName}</h1> <div className="text-3xl my-5">Welcome to your Dashboard.</div> <section className="mt-5"> <div className="text-2xl"> <button className="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" onClick={createNewTask} > New Task </button> </div> {allIssues.length ? ( <Cards issues={allIssues} linkable={session.role !== "admin"} /> ) : ( <div>No issues found.</div> )} </section> {allIssues.length >= 20 && ( <section className="mt-5"> <button onClick={loadMoreIssues} className="bg-gray-200 hover:bg-gray-200 text-green-800 rounded-md px-3 py-2 mt-2" > Load More </button> </section> )} </div> </Layout> ); }
额外注意事项
- 环境变量安全:
NEXT_PUBLIC_前缀的环境变量会暴露给浏览器,敏感令牌不要使用这个前缀,应该在API路由里使用普通环境变量(不带NEXT_PUBLIC_)。 - 权限验证:API路由里的
getServerSession可以确保只有登录用户能访问数据,避免未授权请求。 - 分页逻辑:如果需要实现加载更多,需要修改API路由接收分页参数,在Prisma的
findMany中使用cursor和take实现游标分页。
内容的提问来源于stack exchange,提问作者atomic-ag
相关产品推荐
相关产品推荐

