Why Most Next.js Developers Get Server and Client Components Wrong

When Next.js shipped the App Router, it introduced a distinction that trips up almost every developer the first time: Server Components vs Client Components. I've seen senior engineers get this wrong. I've gotten it wrong. Here's what actually clicks once it does.
The default that nobody reads
Every component in the app/ directory is a Server Component by
default. Not sometimes. Always - unless you explicitly put
'use client' at the top of the file.
This matters because Server Components run on the server, have zero
JavaScript sent to the browser, can await database calls directly,
and cannot use hooks or browser APIs. Client Components are the
opposite, they run in the browser, can use useState, useEffect,
event handlers, and all the React you already know.
The mistake most developers make is slapping 'use client' on
everything the moment something doesn't work. That technically fixes
the error, but it defeats the entire point of the App Router.
The mental model that actually sticks
Ask yourself one question before writing any component:
Does this component need to respond to user interaction or browser APIs?
If yes , go with 'use client'. If no, leave it as a server component.
That's it. Not "does it use JSX", not "is it complex", not "does it import anything". Just: does it need to react to the user?
A navigation bar that highlights the active link? Client Component
, it needs usePathname().
A blog post page that fetches content and renders markdown? Server Component, it just reads data and returns HTML.
A portfolio grid that filters projects by category? Split it — server component fetches the data, client component handles the tab state.
The split pattern
This is the one you'll use constantly once you understand it:
// Portfolio.tsx - Server Component
// Fetches data, no 'use client'
export default async function Portfolio() {
const projects = await getProjects() // direct DB call, no API route
return <PortfolioClient initialProjects={projects} />
}
// PortfolioClient.tsx - Client Component
'use client'
// Handles filtering, state, interactions
export default function PortfolioClient({ initialProjects }) {
const [activeTab, setActiveTab] = useState('all')
// ...
}The server component owns the data. The client component owns the interactivity. Neither knows more than it needs to.
Why this matters beyond performance
Yes, Server Components reduce your JavaScript bundle. Yes, they're faster. But the more important benefit is architectural clarity.
When you enforce this split, you end up with components that have one job. Your data fetching logic stops bleeding into your UI logic. You stop building API routes for things that could just be a direct database call in a server component. Your codebase becomes easier to reason about.
I rebuilt my portfolio with this approach recently. The entire projects section went from a useEffect-driven client component hitting an API route, to a server component making a direct Supabase call and passing the result down. Less code, faster page, cleaner architecture.
The one rule that prevents most errors
Never import a Client Component into a Server Component and expect
the server component to remain a server component. The moment you
do that without a proper boundary, the client component's
'use client' declaration propagates upward.
The correct pattern is always: Server Component -> passes data as props -> Client Component. Never the other way around.
Once this mental model is solid, the App Router stops being confusing and starts being genuinely powerful. It's not React with extra steps, it's a fundamentally different way of thinking about where your code runs.
If this clicked for you or you've been burned by this before, I'd like to hear about it. I write about full-stack development, architecture decisions, and building production apps on a budget at kebleinsyt.com.ng/blog.
Share on LinkedIn
Let your network read this post
Joshua Kaycee
Full-stack developer · kebleinsyt.com.ng