•7 min read

React Server Components vs Client Components: A Deep Dive

React Server Components vs Client Components: A Deep Dive

The release of React Server Components (RSC) fundamentally shifted the architectural paradigms of React applications. Before RSC, React primarily focused on rendering interactive components in the browser (or occasionally pre-rendering them to HTML via SSR). With React Server Components, we now have a distinct split: components that execute exclusively on the server, and components that execute on the client.

In this guide, we'll break down the core differences, the architectural benefits of this hybrid approach, and the decision matrix for when to use Server versus Client components.

Audio Briefing
0:00 / --:--

What are React Server Components?

React Server Components are components that run only on the server. They are never sent to the browser as JavaScript. Instead, the server executes the React component logic, fetches any necessary data directly from databases or internal APIs, and streams the resulting HTML/UI directly to the client.

Advantages of Server Components

  1. Zero Client-Side JavaScript: Because Server Components never execute in the browser, they do not add any weight to your application's JavaScript bundle. You can import massive libraries (like heavy markdown parsers or date formatting libraries) inside a Server Component without punishing the user's network connection.
  2. Direct Data Access: Server components execute in a trusted environment. You can securely query your database directly from your component without needing to spin up an intermediary API route or deal with useEffect data-fetching waterfalls.
  3. Improved Initial Load Times: The server can stream the rendered UI to the client incrementally, allowing users to see and interact with the page much faster than waiting for a large JS bundle to download and hydrate.

The Catch: No Interactivity

Because Server Components never run in the browser, they cannot use browser-only APIs or React hooks that depend on client-side state. You cannot use useState, useEffect, onClick, or window.localStorage inside a Server Component.

What are Client Components?

Client Components are the standard React components you are already familiar with. They are sent to the browser as JavaScript, where they are executed and hydrated to provide rich interactivity. In Next.js App Router, you explicitly mark a component as a Client Component by placing the "use client" directive at the very top of the file.

When to Use Client Components

You must use Client Components whenever your UI requires interactivity or client-side state:

  • Handling user events like onClick, onChange, or form submissions.
  • Managing local state with useState or complex logic with useReducer.
  • Utilizing side effects via useEffect (e.g., subscribing to a WebSocket or interacting with the DOM).
  • Using browser APIs like Geolocation or IntersectionObserver.
Advertisement

The Optimal Hybrid Architecture

The true power of modern React lies in combining both paradigms. The best practice is to build your application tree entirely out of Server Components, and then "sprinkle" Client Components only at the specific leaves of the tree that require interactivity.

Example: A Blog Post Layout

Imagine building a blog post page.

  • The Layout, Navigation, and the Blog Content itself are entirely static and require data fetching from a CMS or database. These should all be Server Components.
  • However, at the bottom of the post, you have a "Like Button" and a "Comment Section". These require onClick handlers and client-side state. You would extract only these pieces into separate files, mark them with "use client", and import them into your Server Component layout.
tsx
// This is a Server Component (default in Next.js)
import db from '@/lib/db';
import LikeButton from './LikeButton'; // Client Component

export default async function BlogPost({ id }) {
  // Direct, secure database access
  const post = await db.post.findUnique({ where: { id } });

  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      
      {/* We pass static data to an interactive Client Component */}
      <LikeButton initialLikes={post.likes} postId={post.id} />
    </article>
  );
}

Related Reading: If you're struggling with SSR, check out this guide on fixing Next.js Hydration Errors.

You Might Also Like

Frequently Asked Questions

No. Because Client Components run in the browser, any component they import will also be forced to run in the browser (and be included in the JS bundle). However, you can pass a Server Component as a children prop to a Client Component, preserving the Server Component's environment.
Yes. In frameworks like Next.js, Client Components are still pre-rendered on the server to generate initial HTML (SSR), but they are also sent to the client to be hydrated with interactivity. Server Components, on the other hand, are only rendered on the server and never hydrated on the client.
Server Components are stateless. You can pass static data (like a database query result) down to a Client Component via props. If you need global interactive state (like a shopping cart or user theme), that state must be managed entirely within the Client Component tree using libraries like Zustand, Jotai, or React Context.
SSR pre-renders interactive Client Components to initial HTML on the server, but still transfers the entire JavaScript bundle to the browser so React can hydrate state and event listeners. RSC executes exclusively on the server and streams a specialized UI format (the RSC payload); its dependencies and code are never downloaded to the client browser, resulting in zero client bundle size overhead.
Yes. While a Client Component cannot directly import a Server Component, you can pass a Server Component as a child slot: <ClientWrapper><ServerPostContent /></ClientWrapper>. The server component executes on the backend, and its rendered virtual tree is seamlessly slotted into the interactive client boundary.
Server Actions are asynchronous functions marked with 'use server' that execute securely on the server. Interactive Client Components can invoke them directly inside form submissions or event handlers to mutate database records, automatically triggering revalidation of surrounding Server Component data trees.
By keeping heavy dependencies (such as date libraries, syntax highlighters, and data transformation packages) entirely on the server, RSC cuts client JavaScript payloads by 30-70%. Less client script execution frees the browser main thread, accelerating Largest Contentful Paint (LCP) and drastically reducing Interaction to Next Paint (INP) latency.
Props passed across the boundary must be serializable. Strings, numbers, booleans, plain objects, arrays, and JSX elements are supported. You cannot pass functions, class instances, Symbols, or Date objects directly without prior serialization.
Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement
Dev Kits & DataToolsTagsRSSPrivacy PolicyTerms