Contact

Exploring React Server Components: My First Impressions

November 25, 2025
Nick Paolini
4 min read
ReactNext.jsServer ComponentsWeb Development
Exploring React Server Components: My First Impressions

I've been hearing about React Server Components for a while now, and I finally took the plunge to really understand them. After building a small project with Next.js 15 and the App Router, I'm genuinely excited about where this is headed.

What Are Server Components?

Server Components are React components that render exclusively on the server. Unlike traditional React components that ship JavaScript to the client, Server Components:

  • Run only on the server
  • Have direct access to backend resources (databases, filesystems, etc.)
  • Don't add to your client-side bundle size
  • Can't use client-side features like useState or useEffect

Here's a simple example:

// This is a Server Component by default in Next.js App Router
async function BlogPost({ slug }: { slug: string }) {
  // Direct database access - no API route needed!
  const post = await db.post.findUnique({ where: { slug } })
 
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

The Mental Shift

The biggest challenge for me was unlearning patterns I've been using for years. I kept reaching for useEffect to fetch data, but with Server Components, you just... fetch it. Right there in the component.

// Old way (Client Component)
function Posts() {
  const [posts, setPosts] = useState([])
 
  useEffect(() => {
    fetch('/api/posts')
      .then(res => res.json())
      .then(setPosts)
  }, [])
 
  return <PostList posts={posts} />
}
 
// New way (Server Component)
async function Posts() {
  const posts = await db.post.findMany()
  return <PostList posts={posts} />
}

No loading states, no API routes, no client-side fetch. It's beautifully simple.

When to Use Client Components

Not everything can be a Server Component. You need Client Components when you have:

  • Interactive elements (onClick, onChange, etc.)
  • Browser APIs (window, localStorage, etc.)
  • State management (useState, useReducer, etc.)
  • Effects (useEffect, useLayoutEffect, etc.)

The pattern I've settled on is to start with Server Components by default and only add 'use client' when I need interactivity.

// ServerComponent.tsx (default)
import { LikeButton } from './LikeButton'
 
async function BlogPost({ slug }: { slug: string }) {
  const post = await db.post.findUnique({ where: { slug } })
 
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <LikeButton postId={post.id} />
    </article>
  )
}
 
// LikeButton.tsx (client component)
'use client'
 
export function LikeButton({ postId }: { postId: string }) {
  const [liked, setLiked] = useState(false)
 
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  )
}

Performance Benefits

The performance wins are real. My test app went from a 180KB initial bundle to 120KB, just by moving data fetching to the server. That's a 33% reduction in JavaScript sent to the browser.

Plus, the data fetching happens on the server, which is typically much closer to your database than your users are. Faster queries, faster page loads.

The Streaming Magic

One feature that blew my mind is streaming. You can start sending HTML to the client before all your data is ready:

import { Suspense } from 'react'
 
async function SlowComponent() {
  // This takes 3 seconds
  const data = await fetchSlowData()
  return <div>{data}</div>
}
 
function Page() {
  return (
    <div>
      <h1>My Page</h1>
      <p>This loads instantly</p>
 
      <Suspense fallback={<div>Loading...</div>}>
        <SlowComponent />
      </Suspense>
    </div>
  )
}

The header and paragraph show up immediately, and the slow component streams in when it's ready. The user sees content faster, and the perceived performance is incredible.

What I'm Still Figuring Out

  • Data mutations: The new Server Actions pattern is powerful but feels weird coming from REST APIs
  • Caching: Next.js caches aggressively by default, which is great for performance but tricky to reason about
  • Testing: How do you properly test async Server Components?

My Take

Server Components aren't just a new feature - they're a fundamental shift in how we build React apps. The mental model takes some getting used to, but once it clicks, everything feels more natural.

I'm not saying we should rewrite everything with Server Components, but for new projects? I'm all in. The performance benefits alone make it worth learning.

If you haven't tried them yet, I highly recommend spinning up a Next.js 15 project and building something small. The best way to learn is by doing.

Resources

What's your experience with Server Components? I'd love to hear your thoughts!