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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:54:53