# 如何修复AI将服务器代码放入客户端组件

> AI代理将数据库查询、秘密环境变量和Node.js API泄露到'use client'组件中，将仅服务器端逻辑暴露给浏览器包。

**Type:** Failure  
**Tools:** Cursor, Claude Code, Codex, Windsurf  
**Stack:** Next.js, TypeScript  
**Updated:** 2026-06-08

---

代理向导入Prisma、`fs`或秘密环境变量的组件添加`"use client"`指令——这会将那段代码发送给每个访问者的浏览器包。

## 症状

标记为`"use client"`的组件执行了直接数据库调用或读取了`process.env`秘密。

```tsx
"use client";
import { db } from "@/lib/db"; // Prisma client — Node.js only
import { useState, useEffect } from "react";

export function UserCard({ id }: { id: string }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // db is a Node.js module — this will throw at runtime in the browser
    db.user.findUnique({ where: { id } }).then(setUser);
  }, [id]);

  return <div>{user?.name}</div>;
}
```

构建可能成功（Next.js会打包导入），但页面在浏览器中抛出运行时错误，因为`@prisma/client`需要Node.js API。

## 原因

代理发现一个同时使用状态或效果的组件内部需要数据，便直接使用它所熟知的数据层。它没有建模服务器/客户端边界，也不知道`"use client"`组件不能导入仅Node.js模块。

## 如何发现

- `"use client"`位于文件顶部，同时导入来自`@/lib/db`、`prisma`、`fs`、`path`或`crypto`。
- 在`"use client"`文件中读取了`process.env.DATABASE_URL`或任何`_SECRET_`变量。
- 构建输出包含`Critical dependency: the request of a dependency is an expression`或Prisma打包警告。
- 项目中缺少`server-only`包。

## 修复方法

拆分为一个负责数据获取的服务器组件父组件，和一个处理交互的客户端组件子组件。

```tsx
// app/users/[id]/page.tsx — Server Component (no directive)
import { db } from "@/lib/db";
import { UserCard } from "./UserCard";

export default async function UserPage({ params }: { params: { id: string } }) {
  const user = await db.user.findUniqueOrThrow({ where: { id: params.id } });
  // Serialize — pass plain data, not the Prisma object
  return <UserCard name={user.name} email={user.email} />;
}
```

```tsx
// app/users/[id]/UserCard.tsx — Client Component
"use client";
import { useState } from "react";

export function UserCard({ name, email }: { name: string; email: string }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <div>
      <p>{name}</p>
      {expanded && <p>{email}</p>}
      <button onClick={() => setExpanded((v) => !v)}>Toggle</button>
    </div>
  );
}
```

```txt
[ ] Install "server-only" and import it at the top of every server-side lib file
[ ] No db/prisma imports in "use client" files
[ ] No process.env secrets read in "use client" files
[ ] Server Component fetches data; Client Component receives plain props
[ ] Use Server Actions (not useEffect+fetch) when a client interaction needs db access
```

## 修复提示

```txt title="Fix Prompt"
This "use client" component imports server-only modules (Prisma, fs, or secret
env vars). Refactor it: move all data fetching into an async Server Component
parent, pass only serializable props to the client component, and keep "use
client" only on the part that needs browser APIs or React state. Add
"server-only" to any shared lib files that must never reach the browser.
```

## 测试

```bash
# List all "use client" files that also import known server-only packages
grep -rl '"use client"' app/ | xargs grep -l "prisma\|@/lib/db\|\"fs\"\|\"path\"\|\"crypto\"" && echo "FAIL: server code in client component" || echo "OK"
```