almessadi.
Back to Index

React Server Components vs SSR: Where the Boundary Actually Is_

SSR and React Server Components solve related problems, but they are not the same mechanism. Understanding the boundary helps with bundle size, data flow, and architecture.

PublishedMarch 20, 2024
Reading Time10 min read

React Server Components and server-side rendering are easy to blur together because they are often used in the same application.

They are related. They are not the same thing.

If you treat them as interchangeable, you end up with confusing architecture decisions and a codebase that ships more client JavaScript than it needs to.

SSR Solves Initial Rendering

Traditional SSR renders HTML on the server so the browser can display something useful before the client application fully hydrates.

That is valuable for:

  • first paint
  • crawlability
  • perceived performance

But SSR does not remove the need for client JavaScript. If the page is interactive, the browser still needs the code required to hydrate that UI.

That is the key point many teams miss: server-rendered HTML is not the same thing as server-only component execution.

Server Components Solve Bundle Placement

React Server Components change where some component logic is allowed to live.

A Server Component runs on the server, can access server-side resources directly, and does not ship its own implementation to the browser.

That makes it useful for:

  • database-backed UI assembly
  • expensive content transformation
  • keeping server-only dependencies out of the client bundle

A simple example:

import { db } from "@/lib/db";
import MarkdownIt from "markdown-it";

const md = new MarkdownIt();

export default async function PostBody({ slug }: { slug: string }) {
  const post = await db.post.findUniqueOrThrow({ where: { slug } });
  const html = md.render(post.body);

  return <article dangerouslySetInnerHTML={{ __html: html }} />;
}

If this stays a Server Component, the browser does not need the database client or the Markdown parser just to make the rest of the page interactive.

Hydration Still Exists

Server Components do not eliminate client components. They let you use them more intentionally.

The moment you need browser APIs, local state, or event handlers, you cross into client territory:

"use client";

import { useState } from "react";

export function LikeButton() {
  const [liked, setLiked] = useState(false);

  return (
    <button onClick={() => setLiked((value) => !value)}>
      {liked ? "Liked" : "Like"}
    </button>
  );
}

That component still hydrates on the client. The goal is not "zero JavaScript everywhere". The goal is to ship JavaScript only where the browser actually needs it.

The Architecture Decision

The practical question is not "should we use SSR or RSC?" In modern Next.js applications, you often use both.

The better question is:

"Which parts of this tree need to run in the browser?"

If the answer is "very little", Server Components can materially reduce bundle size and dependency sprawl.

If most of the page is highly interactive, Server Components still help with data access and composition, but they do not magically turn an interactive dashboard into a server-only page.

What Goes Wrong in Real Codebases

The common failure modes are predictable:

  • marking entire routes with "use client" because one leaf needs interactivity
  • passing large serialized objects across the server/client boundary
  • importing server-only modules into client code by accident
  • assuming Server Components replace all API boundaries

The discipline is to keep the boundary narrow.

Use Server Components for:

  • data fetching
  • server-only libraries
  • non-interactive content assembly

Use Client Components for:

  • event handlers
  • browser-only APIs
  • local interaction state

That split is not ideological. It is just easier to scale.

Further Reading