Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
ReactNext.jsJune 21, 20264 min read

React Server Components vs Client Components in Next.js

React Server Components vs Client Components: Learn how to manage the Next.js network boundary, optimize your frontend architecture, and boost performance.

ReactNext.jsFrontendWeb DevelopmentPerformanceTutorial
Coding on a laptop outdoors, showcasing a rooftop urban lifestyle in Surat, India.

I remember staring at a "Module not found" error for three hours during my first week working with the Next.js App Router. I was trying to use a useState hook inside a component that was, unbeknownst to me, a Server Component. It felt like I was fighting the framework, but once I grasped the network boundary, everything clicked.

Understanding React Server Components (RSC) and Client Components isn't just about reading documentation; it's about shifting your mental model of where your code lives.

The Mental Shift: Where Does Code Run?

Before the App Router, we were used to everything being "client-side." Even with SSR, the components were essentially client-side code rendered on the server once. With React Server Components, the paradigm has changed.

RSC code runs exclusively on the server. It never ships to the browser. When you use a library like fs to read a file or query a database directly, you're doing it in an RSC. This creates a hard network boundary. If a component needs interactivity—like onClick handlers or useEffect—it must be a Client Component.

We often think of this as:

  • Server Components: Data fetching, security, direct database access, zero-bundle size.
  • Client Components: Interactivity, browser APIs, state management, event listeners.

If you are struggling with performance bottlenecks, it's worth reviewing how you handle data fetching to avoid Next.js App Router Data Fetching: Avoiding Performance Waterfalls.

Navigating the Network Boundary

The biggest mistake I see juniors make is trying to pass functions as props from a Server Component to a Client Component. It simply doesn't work. The network boundary acts like a serialization layer. You can pass data (props) from server to client, but you cannot pass functions or complex class instances across the wire.

When I first refactored a large dashboard, I hit a wall because I tried to pass a database-querying function into a Button component. It broke. I had to pivot to using Next.js App Router Server Actions for Atomic State Synchronization to handle those interactions safely.

When to Use Which?

Here is a simple heuristic I use for React rendering:

  1. Default to Server Components: If it doesn't need interactivity, keep it on the server. You'll save roughly 20-30KB of JS bundle per component, which adds up fast.
  2. "Use Client" is a Directive, Not a Suggestion: Placing "use client" at the top of your file tells Next.js that this component, and everything it imports, should be included in the client-side bundle.
  3. Composition is Key: You can import a Server Component into a Client Component, but you must pass the server component as a children prop. This is a common pattern for layout wrappers.

If you're still feeling unsure about the architectural trade-offs, reading this Server components vs client components: A practical guide will give you a deeper look at the implementation details.

A Practical Example: The Hybrid Approach

Let's look at a common scenario: a search bar that fetches data.

TSX
// SearchBar.tsx(Client Component)
CE9178">'use client';
import { useState } from CE9178">'react';

export default function SearchBar({ onSearch }) {
  const [query, setQuery] = useState(CE9178">'');
  return (
    <input 
      value={query} 
      onChange={(e) => setQuery(e.target.value)} 
      onKeyDown={(e) => e.key === CE9178">'Enter' && onSearch(query)} 
    />
  );
}

You can't fetch data inside SearchBar if you want to keep your SEO and loading states managed by the server. Instead, you fetch the data in a Server Component and pass the results down.

Why This Architecture Matters

By separating concerns, your web development workflow becomes more predictable. You stop worrying about useEffect loops for data fetching because the data is already there by the time the component renders.

However, don't over-engineer. Sometimes, keeping a component as a Client Component is just simpler. If you're building a highly interactive UI, don't force it into a Server Component just to save a few bytes. Performance is about trade-offs, and developer productivity is a metric too.

I still find myself occasionally marking a component as "use client" only to realize I didn't actually need it. The best part? It takes about 10 seconds to move the logic back to the server. Don't be afraid to experiment, break things, and refactor. That's the only way you'll truly internalize the boundary.

What I'm still learning? Mastering the complexity of streaming. When you have nested RSCs fetching data at different speeds, the orchestration can get tricky. But for now, focus on keeping your server components lean and your client components strictly for interaction.

Back to Blog

Similar Posts

A minimalist rendering of a white cube with a geometric pattern on a blue background.
ReactNext.jsJune 21, 20264 min read

React Rendering Lifecycle: Why Components Re-render and How to Optimize

Master the React rendering lifecycle to stop unnecessary updates. Learn why react re-renders happen and how to optimize your components for better performance.

Read more
Serene long exposure of a cascading waterfall surrounded by lush greenery in Shifen, Taiwan.
Next.jsReactJune 20, 20264 min read

Next.js App Router Data Fetching: Avoiding Performance Waterfalls

Learn how to master Next.js App Router data fetching by parallelizing server requests. Stop blocking your renders and fix performance waterfalls today.

Read more
Scrabble tiles spelling 'online store' on a rustic wooden background.
Next.jsReactJune 20, 20264 min read

Next.js Partial Prerendering: Optimizing Dynamic E-commerce Feeds

Next.js Partial Prerendering (PPR) lets you mix static and dynamic UI in one route. Learn how to optimize your e-commerce product feeds for instant loading.

Read more