How to Fix AI Putting Server Code in Client Components
AI agents leak database queries, secret env vars, and Node.js APIs into 'use client' components, exposing server-only logic to the browser bundle.
The agent adds a "use client" directive to a component that imports Prisma,
fs, or a secret env var — which ships that code to every visitor’s browser
bundle.
The symptom
A component marked "use client" performs a direct database call or reads a
process.env secret.
"use client";import { db } from "@/lib/db"; // Prisma client — Node.js onlyimport { 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>;}The build may succeed (Next.js bundles the import) but the page throws a
runtime error in the browser because @prisma/client requires Node.js APIs.
Why it happens
The agent sees that data is needed inside a component that also uses state or
effects, and reaches for the data layer it knows. It does not model the
server/client boundary or know that "use client" components cannot import
Node.js-only modules.
How to spot it
"use client"at the top of a file that also imports from@/lib/db,prisma,fs,path, orcrypto.process.env.DATABASE_URLor any_SECRET_variable read inside a"use client"file.- Build output contains
Critical dependency: the request of a dependency is an expressionor a Prisma bundle warning. server-onlypackage is absent from the project.
How to fix it
Split into a Server Component parent that fetches, and a Client Component child that handles interactivity.
// 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} />;}// 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> );}[ ] 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 accessFix Prompt
This "use client" component imports server-only modules (Prisma, fs, or secretenv vars). Refactor it: move all data fetching into an async Server Componentparent, pass only serializable props to the client component, and keep "useclient" 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.Test
# List all "use client" files that also import known server-only packagesgrep -rl '"use client"' app/ | xargs grep -l "prisma\|@/lib/db\|\"fs\"\|\"path\"\|\"crypto\"" && echo "FAIL: server code in client component" || echo "OK"