⏱️ Reading time: 15 min

React 19 no longer ships all your code to the browser. Since React Server Components became stable, an entire part of your application can live only on the server and never get bundled into the JavaScript the user downloads. That breaks a rule that had been fixed in frontend development for more than a decade: that every React component runs, sooner or later, in the browser.

📑 En este artículo
  1. TL;DR
  2. What Is React Server Components?
  3. Why Server Components Matter
  4. How RSC Works Under the Hood
  5. Practical Examples
  6. Getting Started
  7. Real-World Use Cases
  8. Common Mistakes and Best Practices
  9. Comparison with Other Rendering Approaches
  10. Going Deeper: The RSC Payload and Streaming
  11. Frequently Asked Questions
    1. Do React Server Components Replace Next.js?
    2. Can I Use Server Components Without a Framework?
    3. Does RSC Improve SEO?
    4. What’s the Difference Between a Server Component and Traditional Server-Side Rendering?
    5. Does Create React App Support RSC?
    6. Can Server Components Query a SQL Database Directly?
  12. References

The piece that makes this possible is React Server Components: pieces of interface that run exclusively on the server, read data directly, and never reach the client bundle. They don’t replace React, they split it into two environments with an explicit boundary between what runs on the server and what runs in the browser.

TL;DR

  • A React Server Component runs only on the server and never enters the browser’s JavaScript bundle.
  • The ‘use client’ directive marks the exact boundary where the code that does reach the browser begins.
  • Next.js enabled server components by default in the App Router starting with version 13.4 (2023).
  • Saving data with a Server Action doesn’t require writing a single line of REST API code.
  • The RSC Payload serializes the server tree and streams it to the browser.

What Is React Server Components?

React Server Components are a type of React component that runs exclusively on the server during rendering, without including its code in the JavaScript bundle the browser downloads. They have no access to state hooks or browser events: their job is to read data and produce interface.

The idea of rendering on the server isn’t new (2000s era PHP already did it), but React solves it differently: the server and the browser share the same component tree, and a component can delegate the part that needs interactivity to another one marked with 'use client'. The result is a single application with two execution environments, not two separate applications communicating through a classic REST API.

Why Server Components Matter

The practical reason is bundle weight. Before RSC, showing a list that comes from a database forced React to send the browser the component code, the code to fetch the data (fetch calls, error handling, loading states), and then run all of that on the user’s device. With a server component, that entire code stays on the server side: the browser only receives the already rendered result.

That matters doubly on low-end devices or slow networks, where the real cost isn’t just downloading the JavaScript but parsing and executing it before the page responds to a click. A component that never reaches the browser carries none of that cost, no matter how much logic it has inside.

It also changes data security. A server component can import a database client or an API key directly, because that code never gets serialized to the client. Before, exposing that same logic without an intermediate backend would have leaked the key inside the bundle.

And it changes the developer experience: there’s no longer a need to stand up an API route just so a component can read data. The server component calls its data source directly, and that network trip never leaves the server.

How RSC Works Under the Hood

When the browser requests a page, the server starts rendering the component tree from the root. Every component without the 'use client' directive is, by default, a server component: React runs it right there, resolves its await calls, and produces a serialized representation called the RSC Payload, which isn’t HTML yet. That representation includes the markup of the server components and references to the client component modules it finds along the way.

The framework (in practice, almost always Next.js) takes that payload and generates the initial HTML for the first load, along with the minimum JavaScript needed to hydrate only the client components. Hydration no longer covers the entire page: React skips the server components because they have no state to reactivate, and only wires up the event listeners for components marked with 'use client'.

For subsequent navigations within the same session, the browser doesn’t request full HTML again: it requests the updated RSC Payload directly, applies it to the tree it already has in memory, and updates only what changed, without losing the state of the client components that weren’t touched.

React 19 became stable on December 5, 2024, moving RSC out of its experimental phase. Foto de Ferenc Almasi en Unsplash
flowchart TD
    A["Browser requests the page"] --> B["Next.js server"]
    B --> C["Server component"]
    C --> D["fetch to a data source"]
    D --> C
    C --> E["Serialized RSC Payload"]
    E --> F["Initial HTML + client component references"]
    F --> A

Practical Examples

The simplest example is a component that reads data and displays it, with no state at all. It doesn’t need the 'use client' directive because it doesn’t use any hooks:

// app/posts/page.js
export default async function PostsPage() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=3');
  const posts = await res.json();

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

This component runs on the server on every request, fetches the data, and the browser receives the HTML directly with the list already built. The output in the page’s source code (right-click, view source) is literally this:

<ul>
  <li>sunt aut facere repellat provident occaecati excepturi optio reprehenderit</li>
  <li>qui est esse</li>
  <li>ea molestias quasi exercitationem repellat qui ipsa sit aut</li>
</ul>

Now let’s add interactivity. A “like” button needs state, and state only exists in client components, which is why it carries the directive at the top of the file:

// app/posts/LikeButton.js
'use client';

import { useState } from 'react';

export default function LikeButton() {
  const [likes, setLikes] = useState(0);

  return (
    <button onClick={() => setLikes(likes + 1)}>
      Like ({likes})
    </button>
  );
}

When the page loads, that button initially renders as <button>Like (0)</button>. Each click adds one in the browser, without touching the server again, because that state lives only there.

The real-world case combines both: a server component that renders the list and, inside it, imports the client button for each item. React allows this mix because a server component can import and render client components (the reverse, a client component importing a server one, isn’t allowed).

The last step up are Server Actions: functions marked with 'use server' that a form can invoke directly, without the developer writing an API route:

// app/actions.js
'use server';

export async function addComment(formData) {
  const text = formData.get('comment');
  await db.comment.create({ data: { text } });
}
// app/posts/CommentForm.js
import { addComment } from '../actions';

export default function CommentForm() {
  return (
    <form action={addComment}>
      <input name="comment" />
      <button type="submit">Comment</button>
    </form>
  );
}

When the form is submitted, React serializes the data, calls addComment on the server, and re-renders the affected tree. There’s no manual fetch or endpoint to maintain: the server function and the client form stay connected through that reference.

sequenceDiagram
    participant U as User
    participant N as Browser
    participant S as Server
    U->>N: submits the form
    N->>S: invokes addComment with the data
    S->>S: runs addComment and saves the comment
    S-->>N: returns the updated tree
    N-->>U: the UI updates without reloading

Getting Started

To actually try RSC you need a framework that implements it: React alone doesn’t come with a server. The most direct path today is Next.js, which uses server components by default in the App Router starting with version 13.4. Before you start, you need Node.js 18.18 or higher (check it with node -v).

npx create-next-app@latest rsc-demo

The installer asks several questions in the terminal: TypeScript (you can answer No to follow this article’s examples), ESLint (Yes), Tailwind CSS (No), use a src/ folder (No), and whether you want to use the App Router (answer Yes, that’s the one that brings RSC). When it’s done, go into the folder and start the development server:

cd rsc-demo
npm run dev

The terminal shows something like - Local: http://localhost:3000. Open that URL and you’ll see Next.js’s default welcome page, served by a tree of server components.

To confirm you’re really using RSC and not just traditional SSR, open the browser’s developer tools, go to the Network tab, reload the page, and open the raw HTML response (right-click, view source). The content should already be present in the HTML, with no empty placeholders. Then, in the same Network tab, open the .js files the page downloads and search for the name of a server component you wrote: it shouldn’t appear in any bundle, because that code never left the server.

💡 Tip: if you need to confirm which environment a specific file runs in, import 'server-only' (a package published by the Next.js team itself) at the top of the file: if someone imports it by mistake from a client component, the build fails instead of silently leaking the code.

Real-World Use Cases

A dashboard that reads directly from an internal database without going through a public API is the most commonly cited case: the server component runs the SQL query or calls the ORM, and the browser only receives the already built table. No database credential ever reaches the client.

A blog or documentation site, where content changes rarely but needs to be indexed well by search engines, benefits from receiving complete HTML from the first response without paying the cost of hydrating every paragraph. Interactive elements (a search bar, a copy-code button) stay isolated in specific client components.

A checkout form with validations that need to touch a payment system is a good candidate for Server Actions: the sensitive logic (checking a coupon, charging a card) runs on the server without exposing any key, and the form still feels instant because React handles the transition without reloading the page.

An admin panel with frequently updated tables also benefits: each row can be a server component that fetches fresh data again on every internal navigation, while the filter and sort controls stay in a separate client component that doesn’t need to touch the server on every keystroke.

Common Mistakes and Best Practices

The most frequent mistake is using a state hook inside a component that doesn’t carry 'use client'. Next.js detects it at compile time and breaks the build with a message like: Error: useState only works in a Client Component. The fix is to move that specific piece to its own file with the directive, instead of marking the entire tree as client.

Marking 'use client' too high up in the tree is another typical mistake: if the root layout carries the directive, everything hanging from it becomes a client component, and the entire benefit of RSC is lost. The recommended practice is to push the boundary as far down as possible, ideally to the specific component that needs the hook or the event, not to the container around it.

Passing a regular function as a prop from a server component to a client one also fails, because React can’t serialize an arbitrary function across that boundary. The only functions that cross are Server Actions marked with 'use server', which React knows how to convert into a reference the client can invoke.

Accidentally importing a library meant for Node.js (one that uses fs or environment variables without the public prefix) from a client component leaks that code into the browser bundle. The server-only package mentioned earlier exists precisely so that mistake shows up at build time and not in production.

Assuming that every fetch inside a server component triggers a new request also leads to surprises: during the same render, Next.js automatically deduplicates identical calls, so several components can request the same URL without multiplying the actual traffic to that data source.

Comparison with Other Rendering Approaches

Choosing between CSR, traditional SSR, and server components depends on how much interactivity each part of the screen needs, not the entire application. In practice, almost every real app ends up mixing several of these options across different sections of the same page:

OptionWhen to Use ItAdvantageLimitation
Client-Side Rendering (classic SPA)Highly interactive interfaces where SEO isn’t criticalImmediate interactivity once loadedLarge bundle and blank screen while the JS loads
Traditional Server-Side RenderingPages that need complete HTML and fresh data on every visitFast first paint with real contentThe entire component re-runs on every request, including the non-interactive part
Server Components (RSC)Parts of the UI that read data but don’t need state or eventsZero JavaScript sent for that specific componentCan’t use state hooks or browser listeners
Client Components (‘use client’)Buttons, forms, and any UI with local stateFull access to useState, useEffect, and eventsIts code does travel to the browser and counts toward the bundle

Going Deeper: The RSC Payload and Streaming

The RSC Payload isn’t HTML: it’s its own format, similar to JSON but with special references to client modules, describing the already resolved tree. That separation is what lets the server start sending parts of the tree before finishing resolving everything: if a server component has a slow fetch wrapped in , React sends the rest of the page first and fills in that section when the promise resolves, in the same network trip.

That boundary between server and client is also a hierarchy, not a free mix: a server component can import and render a client one, but a client component can’t directly import a server one (if it tries, Next.js automatically turns it into just another client component, losing the benefit). The correct way to combine them when needed is to pass the already rendered server component as children to the client one.

This explicit boundary is, at its core, the design decision behind all of RSC: instead of the framework guessing which part should render where, the code itself declares its environment with a one-line directive, and React builds the rest of the tree around that declaration. Data revalidation follows the same declarative logic: Next.js lets you tag a fetch with a cache tag and then invalidate only that tag, instead of recalculating the entire page every time a specific piece of data changes.

Next.js marked the App Router, which uses RSC by default, as stable in version 13.4, released in May 2023. Foto de Arnold Francisca en Unsplash

Your next step: take the PostsPage component from this article, run npx create-next-app@latest, and add your own LikeButton to see with your own eyes, in the browser’s Network tab, which code actually travels and which stays on the server.

📬 Get new articles by email

We only email about big articles (1-2 a month).

Frequently Asked Questions

Do React Server Components Replace Next.js?

No. RSC is a feature of React itself; Next.js is one of the frameworks that implements it with a server, a router, and the file conventions needed to make it work. React alone doesn’t include a server.

Can I Use Server Components Without a Framework?

In theory yes, because the specification lives in packages like react-server-dom-webpack, but in practice you need to integrate a bundler and a server that know how to generate and consume the RSC Payload. Almost nobody does it by hand: it’s used through Next.js or another compatible framework.

Does RSC Improve SEO?

It improves time to first meaningful content because the HTML arrives complete from the server, which helps crawlers that don’t execute JavaScript. But SEO also depends on metadata, semantic structure, and overall speed, not just on where each component renders.

What’s the Difference Between a Server Component and Traditional Server-Side Rendering?

Traditional SSR renders the entire tree on the server on every visit, but that same code gets downloaded to the browser again to hydrate. A server component never gets downloaded: its code and its render stay exclusively on the server side forever.

Does Create React App Support RSC?

No. Create React App generates a purely client-side application, and the React team stopped recommending it precisely because it lacks the server part needed for RSC. The recommended frameworks today are Next.js and others that adopt the same architecture.

Can Server Components Query a SQL Database Directly?

Yes, and it’s one of their most common uses: since it never travels to the browser, the component can import a PostgreSQL or MySQL client, run the query, and render the result without exposing the connection string in any public file.

References

  • react.dev: official React reference on Server Components.
  • react.dev/blog: original announcement of React Server Components in 2020.
  • nextjs.org: Next.js documentation on how it implements Server Components in the App Router.
  • react.dev/blog: announcement of stable React 19, December 2024.

📱 Enjoy this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.

Featured image: Foto de Egor Komarov en Unsplash

Did it work for you? Got a different error? Say so below: questions get answered and help the next reader.

Leave a comment
Categories: ProgrammingTutorials

Andrés Morales

Developer and AI researcher. Writes about language models, frameworks, developer tooling, and open source releases. Covers ML papers, the tech startup ecosystem, and programming trends.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

You can include code inside <code>…</code> or, for several lines, <pre><code>…</code></pre>.

This site uses Akismet to reduce spam. Learn how your comment data is processed.