React Server Components vs Client Components

Sanjeev SharmaSanjeev Sharma
4 min read

Advertisement

React Server Components vs Client Components

Server Components and Client Components solve different problems. Understanding when to use each is crucial for building efficient applications.

Architecture Overview

Server Components:

  • Execute on server only
  • Access databases directly
  • Keep secrets server-side
  • Reduced JavaScript bundle
  • Perfect for data fetching

Client Components:

  • Execute in browser
  • Can use React hooks
  • Handle interactivity
  • Access browser APIs
  • Smaller data payloads

When to Use Server Components

// Server Component - fetch data
export default async function UserProfile() {
  const user = await db.users.findUnique({ where: { id: '123' } })
  return <div>{user.name}</div>
}

Use for:

  • Pages that fetch data
  • Database queries
  • Secret API keys
  • Large dependencies
  • Static content

When to Use Client Components

'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

Use for:

  • Interactive features
  • React hooks
  • Event listeners
  • Browser APIs
  • Real-time updates

Mixing Both Patterns

// Server Component
async function UserDashboard() {
  const user = await db.users.findUnique({ where: { id: userId } })

  return (
    <div>
      <h1>{user.name}</h1>
      <UserSettings user={user} /> {/* Client Component */}
    </div>
  )
}

// Client Component
'use client'

function UserSettings({ user }) {
  const [settings, setSettings] = useState(user.settings)

  return (
    <div>
      <input value={settings.theme} onChange={(e) => setSettings({...settings, theme: e.target.value})} />
    </div>
  )
}

Performance Implications

Server Components:

  • ✓ No JavaScript for static content
  • ✓ Direct database access
  • ✓ Secrets stay server-side

Client Components:

  • ✓ Instant interactivity
  • ✓ Browser API access
  • ✗ Larger JavaScript bundle

Data Flow Patterns

Pattern 1: Server Component passes data to Client Component

async function BlogPost() {
  const post = await db.posts.findUnique({ where: { id: postId } })

  return <CommentForm post={post} />
}

'use client'

function CommentForm({ post }) {
  const [comment, setComment] = useState('')

  async function handleSubmit() {
    await fetch(`/api/posts/${post.id}/comments`, {
      method: 'POST',
      body: JSON.stringify({ text: comment })
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <textarea onChange={(e) => setComment(e.target.value)} />
      <button>Submit</button>
    </form>
  )
}

Pattern 2: Client Component calls Server Action

'use client'

import { updateUserSettings } from '@/app/actions'
import { useState } from 'react'

export function SettingsForm() {
  const [theme, setTheme] = useState('light')
  const [loading, setLoading] = useState(false)

  async function handleChange(newTheme) {
    setLoading(true)
    await updateUserSettings({ theme: newTheme })
    setLoading(false)
  }

  return (
    <select onChange={(e) => handleChange(e.target.value)}>
      <option>light</option>
      <option>dark</option>
    </select>
  )
}

Common Mistakes

Mistake 1: Server Component in Client Component

// WRONG
'use client'

async function UserInfo() {
  const user = await db.users.find() // Error: can't use async in client
  return <div>{user.name}</div>
}

Fix:

// Move to server
export async function UserInfo() {
  const user = await db.users.find()
  return <div>{user.name}</div>
}

Mistake 2: Non-serializable data

// WRONG
async function Page() {
  const date = new Date()
  return <ClientComponent date={date} /> // Error: Date not serializable
}

// CORRECT
async function Page() {
  const date = new Date().toISOString()
  return <ClientComponent date={date} />
}

Real-World: E-Commerce Page

// app/products/[id]/page.tsx (Server)
async function ProductPage({ params }) {
  const product = await db.products.findUnique({
    where: { id: params.id },
    include: { reviews: true }
  })

  return (
    <div>
      <ProductDetails product={product} />
      <ProductReviews reviews={product.reviews} />
      <AddToCart productId={product.id} />
    </div>
  )
}

// Client Component for interactivity
'use client'

function AddToCart({ productId }) {
  const [quantity, setQuantity] = useState(1)
  const [added, setAdded] = useState(false)

  async function handleAddToCart() {
    await fetch('/api/cart', {
      method: 'POST',
      body: JSON.stringify({ productId, quantity })
    })
    setAdded(true)
  }

  return (
    <div>
      <input type="number" value={quantity} onChange={(e) => setQuantity(Number(e.target.value))} />
      <button onClick={handleAddToCart}>{added ? 'Added!' : 'Add to Cart'}</button>
    </div>
  )
}

FAQ

Q: Can I have Server Components inside Client Components? A: No, but you can pass Server Component results as props.

Q: How do I share state between Server and Client? A: Use URL search params, Server Actions, or data passed as props.

Q: Should all my components be Server Components? A: Yes, by default. Only use 'use client' when necessary.


Mastering the Server/Client component paradigm is essential for modern React development.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro