A common Next.js App Router misconception: 'use client' doesn't make a file client-only — it marks the boundary where the client tree starts.
When I first moved to Next.js App Router, I assumed "use client" meant "this file runs in the browser." It's more nuanced than that, and the distinction matters.
"use client" marks the boundary between the server component tree and the client component tree. Everything in that file, and every module it imports, becomes part of the client bundle.
But here's what tripped me up: you can pass server-rendered data across the boundary as props.
// app/page.tsx — SERVER component
import { ClientWidget } from "@/components/ClientWidget";
export default async function Page() {
const data = await db.query.posts.findMany(); // runs on server
return <ClientWidget posts={data} />; // data serialized to client
}Karanveer Singh Shaktawat
Full Stack Engineer & Infrastructure Architect
Building portfolio, contributing to open source, and seeking remote full-time roles with significant technical ownership.
Pick what you want to hear about — I'll only email when it's worth it.
Did this resonate?
How XState's final states work, what done() events are, and the pattern for composing machines that signal completion to a parent.
The difference between PgBouncer's pooling modes, why transaction mode breaks prepared statements, and which to use for Next.js apps.
// components/ClientWidget.tsx
"use client";
export function ClientWidget({ posts }: { posts: Post[] }) {
const [selected, setSelected] = useState(null); // client state
return ...;
}data is fetched on the server, serialized, and hydrated on the client. The server component has zero JS in the bundle. The client component has state and handlers.
If you import a server-only module (like drizzle-orm or fs) inside a "use client" file, you'll get a build error — and rightly so. The boundary is strict.
But you can also compose in the other direction: server components can be children of client components via the children prop pattern.
// This works — server tree passed as children to a client layout
<ClientLayout>
<ServerContent /> {/* still runs on server */}
</ClientLayout>This is how the portfolio's admin layout works: the outer shell is a client component (it has navigation state), but individual page content is server components that query the DB directly. No API routes needed.