Behind the curtains of React rendering

6 min read

Imagine you're watching a theater play. The curtains open. The lights hit the stage. Some scenes are performed live, others are pre-recorded and projected on a screen, and sometimes, stagehands quietly swap out props while the audience is watching.

That's exactly how rendering works in modern React apps.

Some scenes are prepared in advance, some unfold right in front of your eyes, and others combine both styles to create a seamless experience. Let’s explore how React decides who performs what, when, and where, and why it matters.

I’m aware there are multiple patterns for building websites. To simplify things, the focus would be on the difference between client and server-side rendering and how React Server Components (RSC) can be useful.

Act 0: Static Site Generation (SSG)

Picture this: the entire play is recorded and edited before the tour even begins. The theater doesn’t need live actors or props. Every performance is exactly the same, perfectly timed, and ready the moment you walk in.

That’s how Static Site Generation works. A static website generated at build time. Your pages are built ahead of time, and when users arrive, they get instant access to the finished show.

Imagine a touring production. Instead of performing live in every city, the cast pre-records the entire show. The final, polished version is sent to each venue. Every audience sees the same show instantly, no stagehands, no live improvisation, just press play and enjoy.

What It Means:

  • HTML is generated at build time.
  • Content is cached and served instantly from a CDN.
  • Zero computation needed per request.

Pros:

  • Extremely fast loading.
  • SEO-optimized.
  • Scales effortlessly.

Cons:

  • Not ideal for dynamic content.
  • Requires rebuilds for content updates.

Act 1: Server-Side Rendering (SSR)

You arrive at the theater, and the entire stage is beautifully set: props are in place, the lighting is just right, and the background music is already playing. Everything looks ready for the performance.

The curtain rises, and the actors walk onto the stage, but instead of jumping straight into the performance, they take a few seconds to adjust: syncing with the lighting cues, responding to the audience’s presence, maybe adjusting a misplaced prop. That’s hydration, bringing interactivity and reactivity to an already prepared scene.

What It Means:

  • The server prepares the full page on each request.
  • The browser hydrates it to enable interaction.
  • Good for pages that change often but still need SEO.

Pros:

  • SEO-friendly with up-to-date content.
  • Useful for dynamic data without long build times.

Cons:

  • Slower response than static pages.
  • Higher server load for each request.

Code Example

// Example using a loader from React-router
export async function loader() {
  const data = await fetch('https://api.theater.com/scene/1').then(res => res.json());
  return { scene: data };
}
 
export default function ScenePage({ scene }) {
  return (
    <div>
      <h1> Act One </h1>
      {scene.map((line) => (<h2 key={line.id}>{line.text}</h2>))}
      <button onClick={() => alert('Bravo!')}>Applaud</button>
    </div>
  );
}

Act 2: Client-Side Rendering (CSR)

Now you’re watching an improv play. The stage is empty when you arrive. Scripts and props are delivered while you wait, and actors perform everything live. It takes a bit before the story unfolds, but the performance adapts instantly to your reactions.

What It Means:

  • The browser builds the page using JavaScript.
  • All logic and data fetching happen on the client.
  • Great for apps with real-time interactivity.

Pros:

  • Fully dynamic and interactive.
  • Offloads work from the server.

Cons:

  • Slower first load.
  • Not ideal for SEO.

Code Example:

import { useQuery } from '@tanstack/react-query';
 
export default function ScenePage() {
  const { data: script, isLoading } = useQuery({
    queryKey: ['scene'],
    queryFn: () => fetch('https://api.theater.com/scene/1').then(res => res.json())
  });
 
  if (isLoading) return <p>Actors rehearsing...</p>;
 
  return (
    <div>
      <h1> Act One </h1>
      {script.lines.map((line) => (<h2 key={line.id}>{line.text}</h2>))}
      <button onClick={() => alert('Bravo!')}>Applaud</button>
      <button onClick={() => alert('Encore!')}>Encore</button>
    </div>
  );
}

Act 3: React Server Components (RSC)

This time, Act One is a pre-recorded scene projected with perfect lighting and timing. But Act Two is performed live. It’s a blend: the static story arc is handled by the server, and dynamic, emotional improvisation happens live on stage.

Wait, isn’t this the same as SSR?

Not quite. This is where many folks get tripped up.

SSR renders HTML on the server, but it sends it to the client for hydration, meaning the JavaScript still runs in the browser after the page loads. It’s like preparing the full stage setup remotely, then flying in the entire cast to perform locally again.

RSC never sends the component's JavaScript to the browser. Instead, it renders the component on the server and serializes the result into a lightweight payload, just the final UI output, not the code that created it. The client only receives this result, not the logic. Think of it as a recorded monologue, no actor needs to be present live for the audience to enjoy it.

What It Means:

  • Some parts of the app are rendered on the server.
  • Interactive parts are hydrated on the client.
  • Enables better performance and smaller bundles.

Pros:

  • Great mix of speed and flexibility.
  • Reduces JavaScript sent to the browser.
  • Enables progressive loading.

Cons:

  • Setup is more complex.
  • Not all frameworks support it fully yet.

Code Example:

'use client';
import { useState } from 'react';
 
export default function ClientWrapper({ children }) {
  const [visible, setVisible] = useState(true);
 
  if (!visible) return null;
 
  return (
    <div>
      {children}
      <button onClick={() => setVisible(false)}>Dismiss Scene</button>
      <button onClick={() => alert('cheer actor')}>Cheer Actor</button>
    </div>
  );
}

Live Performance:

// Server-rendered component
export default async function ActOne() {
  const scene = await fetch('https://api.theater.com/scene/1').then(res => res.json());
 
  return (
    <div>
      <h2>{scene.title}</h2>
      <ul>
        {scene.lines.map(line => (
          <li key={line.id}>{line.text}</li>
        ))}
      </ul>
    </div>
  );
}

Connecting the pieces:

import ClientWrapper from './ClientWrapper.client.jsx';
import ActOne from './ActOne.server.jsx';
 
export default function SceneLayout() {
	return (
		<ClientWrapper>
			<ActOne />
		</ClientWrapper>
	);
}

Streaming SSR and Suspense

Imagine you’re watching a play and instead of waiting for the full cast and set to be ready, the stage opens with the first act while others are being prepared behind the scenes.

Act One (Static): A monologue starts while props for Act Two are being arranged.

Act Two (Dynamic): A large set is rolled in mid-performance, without stopping the play.

Act Three (Interactive): An improv scene where the audience gets involved.

Streaming sends scenes as they’re ready, keeping the show going.

Suspense gives the audience a narrator or a preview while waiting, instead of staring at a blank stage.

import React, { Suspense } from 'react';
import ActTwo from './ActTwo';
 
export default function TheaterPlay() {
  return (
    <div>
      <h1>Tonight's Play</h1>
      <Suspense fallback={<p>Act Two loading...</p>}>
        <ActTwo />
      </Suspense>
    </div>
  );
}

How does this apply to my blog?

For the homepage and top articles, I use Static Site Generation, like pre-recording a trailer. These pages load instantly and don’t need constant updates.

For most content, I rely on Server-Side Rendering. My blog integrates with APIs like Unsplash and Notion, so rendering content on the server keeps things performant and SEO-friendly.

Interactive features like comments from Bluesky are handled on the client.

Considering the latest news on React Router announcing support for React Server Component, this change opens the door for future improvements, like moving vendor-heavy UI elements (e.g., syntax highlighting) to the server. It means less code on the client, no hydration required, and a smaller JS bundle overall.

Conclusion

How and where we render React components affects everything, from speed and SEO to user experience. And the most important takeaways here should be that there's no silver bullet. Context will dictate the best approach.

React Server Components bring the power of the stage and screen together, letting you combine performance with flexibility. The browser isn’t the only stage anymore; React makes room for multiple venues.

If you're curious to learn more about RSCs, there are two main articles I default to when needing to recap about RSC:

  • Check out Josh Comeau’s deep dive. It’s well worth a standing ovation.
  • An article by Aurora Scharff that showcases a few examples of the boundaries and the composition between server and client components.

Get posts in your inbox

Subscribe to my Substack newsletter for the latest posts and updates.

Subscribe on Substack

Written by Manu

I am a product-driven JavaScript developer, passionate about sharing experiences in the IT world, from a human-centric perspective.

Follow Me

Other articles