DEV Community

Cover image for Beyond SSR: Demystifying React Server Components for Real‑World Apps
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Beyond SSR: Demystifying React Server Components for Real‑World Apps

Inspired by the insights in Lydia Hallie & Addy Osmani’s Learning Patterns.

Why look beyond classic SSR?

Server‑side rendering (SSR) was a game‑changer for the initial performance of React apps. But as applications grew, teams hit a few pain points:

  1. Bundle duplication – The same components get shipped to both server and client.
  2. Hydration costs – After HTML is streamed, the browser must download JavaScript, parse it, and re‑attach event listeners.
  3. Tight data -- UI coupling – Fetching data inside getServerSideProps (or equivalent) forces full‑page reloads when only a section needed updating.
  4. Limited in‑flight streaming – You can send HTML early, but JavaScript for large chunks often arrives all at once.

React Server Components (RSC) aim to address these issues while preserving React’s developer experience.


What is a Server Component?

A server component is rendered only on the server. It never appears in the client bundle, so there is zero JavaScript cost for that component in the browser.

// app/page.tsx   (Next.js 14+)
import ProductList from "./ProductList"; // ✅ server component

export default async function Page() {{
  const products = await fetchProducts(); // runs on the server
  return (
    <main className="p-6">
      <h1 className="text-3xl font-semibold mb-4">Our catalog</h1>
      {/* ProductList is rendered on the server; 
          the HTML is streamed to the client. */}
      <ProductList products={{products}} />
    </main>
  );
}}
Enter fullscreen mode Exit fullscreen mode

Key characteristics

  • Can await database or API calls directly.
  • Output is HTML only; no client‑side JS bundle.
  • Can render client components as children (marked with "use client").

Automatic Code Splitting (RSC vs. React.lazy())

Traditional React relies on dynamic import patterns:

// Before
import React, {{ lazy, Suspense }} from "react";
const HeavyChart = lazy(() => import("./HeavyChart"));

export function Dashboard() {{
  return (
    <Suspense fallback={{<Spinner/>}}>
      <HeavyChart />
    </Suspense>
  );
}}
Enter fullscreen mode Exit fullscreen mode

With Server Components, React performs tree‑shaking on the server:

// After – HeavyChart is a Server Component
// It is excluded from the client bundle automatically.
import HeavyChart from "./HeavyChart"; // no 'lazy' needed

export function Dashboard() {{
  return (
    <>
      <Summary />
      <HeavyChart /> {/* streamed as HTML; zero JS sent */}
    </>
  );
}}
Enter fullscreen mode Exit fullscreen mode

Because HeavyChart never reaches the browser, code splitting becomes implicit—React ships only the minimal JS required for interactive parts.


SSR vs. Server Components — at a glance

Capability Classic SSR React Server Components
Eliminates client JS for non‑interactive UI ❌ No (hydration needed) ✅ Yes
Can access secure server resources during render ✅ Yes ✅ Yes
Partial streaming without extra bundling config 🚧 Limited ✅ Built‑in
Requires hydration cost in the browser ✅ Always ⚠️ Only for client components
Works with existing useEffect, DOM APIs ✅ Yes ⚠️ Only inside client components
Ships duplicate logic (server + client) ✅ Often ❌ Never for pure server components

Putting it together

Server Components complement — not replace — SSR:

  • SSR is still useful for the shell of your page and for client components that need early HTML.
  • RSC lets you push heavy, non‑interactive work entirely to the server, trimming bundles and hydration time.

In practice you’ll mix them:

// 'layout.tsx' is a server component by default
import "@/styles/globals.css";
import {{ Suspense }} from "react";

export default function RootLayout({{ children }}) {{
  return (
    <html>
      <body>
        <NavBar />              {/* client component */}
        <Suspense fallback={{<Skeleton/>}}>
          {{children}}           {/* could include server + client parts */}
        </Suspense>
      </body>
    </html>
  );
}}
Enter fullscreen mode Exit fullscreen mode

Conclusion

React Server Components let you scale UX and codebases without punishing runtime performance:

  • Smaller client bundles – ship less JavaScript.
  • Faster TTI – fewer hydration bytes, more streaming.
  • Simpler data fetchingawait directly in components.
  • Automatic code splitting – implicit through server‑only execution.

As teams iterate, learning when to choose a client vs. server boundary becomes the new architecture art. Learning Patterns reminds us that patterns evolve; RSC is the natural next turn in React’s performance playbook.

Happy hacking—see you in the diff!

Top comments (0)