Back to Blog
Development13 min read

React Server Components: The Complete Practical Guide for 2025

RSC changes everything about how we think about rendering in React. Here's what they actually are, when to use them vs client components, and the most common pitfalls to avoid.

Growthency

Growthency Team

December 18, 2024

React Server Components Are Not SSR

The most important clarification: React Server Components (RSC) are not the same as Server-Side Rendering (SSR). This confusion causes more architectural mistakes than anything else.

SSR: Renders your entire React tree on the server to HTML on every request. The client then rehydrates the full tree.

RSC: Renders specific components exclusively on the server. Those components never run on the client and send zero JavaScript to the browser.


The Mental Model: Two Component Environments

Server Environment:

  • Has access to Node.js APIs (file system, environment variables, direct database access)
  • Can be async (fetch data directly in the component)
  • Sends no JavaScript to the client
  • Cannot use hooks or browser APIs
  • Default in Next.js App Router

Client Environment:

  • Runs in the browser
  • Has access to browser APIs, event listeners, local state
  • Marked with "use client" directive

`tsx

// Server Component (default in App Router)

export default async function UserProfile({ userId }: { userId: string }) {

const user = await db.user.findUnique({ where: { id: userId } })

return (

{user.name}

)

}

// Client Component

'use client'

import { useState } from 'react'

export function EditButton({ userId }: { userId: string }) {

const [isEditing, setIsEditing] = useState(false)

return (

)

}

`


Why RSC Matters for Performance

Before RSC:

HTML → JS download → JS execute → API call → DB query → Re-render

With RSC:

Server queries DB directly → Server renders HTML with data → User sees content immediately

The waterfall collapses into a single server-side step.

React Server Components architecture diagram

Practical Patterns

Pattern 1: Data Fetching at the Top

`tsx

// app/dashboard/page.tsx (Server Component)

export default async function DashboardPage() {

const [user, stats, recentActivity] = await Promise.all([

getUser(),

getDashboardStats(),

getRecentActivity()

])

return (

)

}

`

Pattern 2: Streaming with Suspense

`tsx

import { Suspense } from 'react'

export default function ProductPage({ id }: { id: string }) {

return (

}>

)

}

`


Common Mistakes and Pitfalls

Mistake 1: Adding "use client" to everything

The most common mistake. This defeats the entire purpose and bloats the bundle. Rule: Start with Server Component. Only add "use client" when you actually need state, effects, or browser APIs.

Mistake 2: Importing a Server Component into a Client Component

This is not allowed. Client Components can only render other Client Components or Server Components passed as props/children.

`tsx

// ❌ This will error

'use client'

import ServerComponent from './ServerComponent'

// ✅ This works — pass Server Component as a prop

'use client'

export function ClientWrapper({ children }: { children: React.ReactNode }) {

return

{children}

}

`


When to Use Each

| Use Server Component when... | Use Client Component when... |

|------------------------------|------------------------------|

| Fetching data | Using useState/useReducer |

| Accessing backend resources | Using useEffect |

| Keeping sensitive data on server | Using browser APIs |

| No user interactivity needed | Handling event listeners |

The default answer in Next.js App Router: Server Component. Override to Client only when necessary.

#react#server components#nextjs#web development#performance

Share this article

Growthency

Growthency Team

The Growthency team helps businesses launch, scale, and grow using modern software, AI tools, and proven digital strategy. We've worked with 200+ startups and growing businesses worldwide.

About Growthency