React

React 19 Server Components: The Good, The Bad, and The Complex

Jordan Lee
Jordan Lee
React Core Contributor
2025-09-1914 min
React 19 modern component architecture

We deployed React 19 with Server Components to production three months ago, and it fundamentally changed how we build React applications. After shipping 8 features using this architecture, we've learned what works beautifully, what breaks unexpectedly, and where the traditional client-side React patterns still make more sense than the new server-first approach.

What Are Server Components Actually?

React Server Components are React components that run exclusively on the server, never shipping their code to the browser. They can directly access databases, file systems, and backend services without API routes as intermediaries. The mental model shift is significant: instead of fetching data client-side with useEffect and managing loading states, you write async components that await data directly. The server renders these components to a special stream format, sends the result to the client, and React reconstructs the UI without ever executing that component code in the browser.

The performance implications are substantial for data-heavy applications. A traditional client-side approach requires sending React component code, waiting for it to execute, making API calls, and then rendering with data. Server Components collapse this into a single network request that includes both the UI structure and the data. Our product detail page went from 340KB of JavaScript (including data-fetching components) down to 89KB because all the data-fetching logic now lives exclusively on the server. First Contentful Paint improved from 2.1 seconds to 0.8 seconds on a 3G connection.

Server and client architecture
Server Components run on the server, dramatically reducing client-side JavaScript

Server vs Client: Drawing the Line

The hardest part of adopting Server Components is understanding the boundary between server and client code. By default in Next.js 13+, all components are Server Components unless you add 'use client' at the top of the file. Server Components can import and render Client Components, but Client Components cannot import Server Components (though they can receive them as children). This creates an architectural pattern where you push interactivity as deep as possible in the component tree, keeping most of your application as Server Components and only adding 'use client' to the interactive leaves.

Here's a practical example showing how to structure a product page with both server and client code working together:

// app/products/[id]/page.tsx (Server Component - default)
export default async function ProductPage({ params }) {
  const product = await db.product.findUnique({ 
    where: { id: params.id } 
  });
  
  return (
    <div>
      <h1>{product.name}</h1>
      <ProductImages images={product.images} />
      <AddToCartButton productId={product.id} />
    </div>
  );
}

// components/AddToCartButton.tsx (Client Component)
'use client';
import { useState } from 'react';

export function AddToCartButton({ productId }) {
  const [isAdding, setIsAdding] = useState(false);
  
  return (
    <button onClick={() => addToCart(productId)}>
      {isAdding ? 'Adding...' : 'Add to Cart'}
    </button>
  );
}

The product page component is a Server Component that directly queries the database—no API route needed. It renders the AddToCartButton Client Component for interactivity. The key insight is that most of your page can be server-rendered; only the interactive buttons and inputs need 'use client'. This pattern reduced our total JavaScript bundle from 487KB to 203KB across our main product flows.

Code editor with React components
Proper separation of Server and Client Components is key to optimal performance

Where Server Components Excel

Server Components are phenomenal for data-heavy UIs that don't require much interactivity. Our dashboard page that displays analytics data is now a pure Server Component that queries 7 different database tables, aggregates the results, and renders charts—all on the server. There's zero client-side data fetching, no loading spinners, and the page is fully rendered when it arrives in the browser. For SEO-critical pages like product listings and blog posts, Server Components ensure the content is always in the initial HTML, no JavaScript execution required.

Another win is backend integration complexity. Previously, creating an API route for every data need meant maintaining parallel codebases: database queries in API routes, type definitions shared between front and back, error handling in two places. Server Components let you colocate data fetching with the UI that consumes it. When you change how a component renders, you change the query in the same file. We deleted 47 API routes from our codebase, eliminating about 3,200 lines of intermediary code that was just plumbing data from database to UI.

Where They Don't Work

Server Components cannot use browser APIs, React hooks like useState or useEffect, or event handlers like onClick. This means highly interactive features still need Client Components. Our drag-and-drop task board, real-time collaboration features, and animation-heavy UI elements are all Client Components because they fundamentally depend on browser APIs and React's client-side reactivity. The architecture forces you to be intentional about where interactivity lives rather than sprinkling useState throughout every component by default.

Third-party libraries are another pain point. Many popular React libraries were built before Server Components existed and use hooks or browser APIs immediately on import, which breaks Server Components. We had to wrap several libraries in Client Component boundaries or find Server Component-compatible alternatives. Over time this will improve as the ecosystem catches up, but today expect some friction when integrating existing libraries into Server Component trees.

Is It Worth It?

For new projects, absolutely yes—Server Components should be your default architecture. The performance benefits are measurable, the developer experience is cleaner once you internalize the mental model, and you're building with React's future direction. For existing applications, the migration effort depends on how data-heavy and SEO-critical your app is. If you're building admin dashboards, e-commerce, or content-heavy sites, the payoff is worth the refactoring. If you're building highly interactive tools with minimal backend data, traditional client-side React might still be simpler. After three months in production, we're convinced Server Components are the right default for most applications—they just require rethinking how you structure React code.

Klaar om te Starten met je Project?

Bij Webzley bouwen we high-performance websites en web applicaties met de nieuwste technologieën. Van MVP tot enterprise platform - wij helpen je van idee tot lancering.

Chat met ons! 💬