MAISON CODE .
/ Tech · React · Next.js · Architecture · Performance

React Server Components: The Architecture of Zero-Bundle

From 'useEffect' waterfalls to 'async/await'. A technical deep dive into RSC, Next.js App Router, Server Actions, and the caching hierarchy.

AB
Alex B.
React Server Components: The Architecture of Zero-Bundle

For 10 years, the React ecosystem drifted towards “Client-Side Everything.” We built massive Single Page Applications (SPAs). We fetched data in useEffect. We showed 10 spinners while the waterfalls resolved. And we sent 500KB of JavaScript to a user on a 3G connection in Brazil, wondering why “Conversion” was low.

React Server Components (RSC) is the correction. It is not just a feature. It is a fundamental architectural shift that moves the center of gravity back to where it belongs: The Datacenter.

At Maison Code Paris, we have migrated massive e-commerce platforms to Next.js App Router. The result? 40% less JavaScript, 2x faster Largest Contentful Paint (LCP), and a developer experience that feels like cheating.

Why Maison Code Discusses This

We do not chase every new framework. We chase Customer LTV. RSC is the first architecture that aligns technical performance with business goals:

  • SEO: Perfect HTML delivery for crawlers (Google Bot loves RSC).
  • Conversion: Faster “Add to Cart” interactions via Optimistic UI.
  • Sustainability: Lower battery drain on user devices by offloading computation to the cloud. We train all our agency partners on this stack because it is the future of High-Performance Commerce.

The Mental Model: The Server as a Component Tree

In the old world (Pages Directory or Create-React-App), the Server sent HTML, and the Client “hydrated” key functionality. In the RSC world, React runs on the Server.

When a user requests /product/123:

  1. The Server renders ProductPage.
  2. It executes await db.query().
  3. It resolves the data.
  4. It serializes the result into a special format (Flight Protocol).
  5. It streams this to the browser.
  6. The browser renders the UI.

Crucially: The code for the Server Component never leaves the server. If you import moment.js (200KB) in a Server Component to format a date, the user downloads 0KB of it. They just see the text “Nov 23, 2025”.

Data Fetching: Death to useEffect

The era of “Loading Spinners” caused by Client-Side logic is over. We no longer create API routes just to feed our own frontend.

The Old Pattern (Client FetcH):

// Client Component
function Product() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch('/api/product').then(res => res.json()).then(setData);
  }, []);
  
  if (!data) return <Spinner />;
  return <div>{data.name}</div>;
}

The New Pattern (RSC):

// Server Component
import { db } from '@/lib/db';

export default async function ProductPage() {
  // Direct DB access. Secure. Fast.
  const data = await db.product.findFirst();
  
  return <div>{data.name}</div>;
}

This runs on the backend. The data is waiting for the user, not the other way around.

Interactivity: The “Use Client” Directive

But static HTML is boring. We need “Add to Cart” buttons. We re-introduce interactivity using Client Components. You mark a file with 'use client'. This tells Next.js: “Include this file in the JavaScript bundle.”

Composition Pattern: The “Dot” Strategy. Do not make the whole page a Client Component. Make the leaves Client Components.

// Server Component (Layout)
export default async function Page() {
  const product = await db.product.findFirst();
  
  return (
    <div>
      <h1>{product.title}</h1> {/* Zero Bundle Size */}
      <PriceDisplay value={product.price} /> {/* Zero Bundle Size */}
      <AddToCartButton id={product.id} /> {/* Client Component (Interactive) */}
    </div>
  );
}

Server Actions: Type-Safe Mutations

How do we submit forms? API Routes? Axios? REST? No. Server Actions. We write a function that runs on the server, and pass it as a prop to the form.

// actions.ts
'use server'

export async function addToCart(formData: FormData) {
  const productId = formData.get('productId');
  await db.cart.create({ productId });
  // Tell local cache to update
  revalidatePath('/cart');
}

// Button.tsx
import { addToCart } from './actions';

export function AddToCartButton() {
  return (
    <form action={addToCart}>
      <button type="submit">Add</button>
    </form>
  )
}

This works without JavaScript enabled (progressive enhancement). If JS loads, Next.js intercepts the submit and effectively makes an API call. But it feels like calling a function.

Optimistic UI: useOptimistic

When the user clicks “Add,” we want instant feedback. We don’t want to wait for the server roundtrip. RSC introduces useOptimistic.

'use client'
import { useOptimistic } from 'react';

export function LikeButton({ likeCount, action }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likeCount,
    (state, newLike) => state + 1
  );

  return (
    <button onClick={async () => {
        addOptimisticLike(1); // Immediate UI update
        await action(); // Server background sync
    }}>
       Likes: {optimisticLikes}
    </button>
  );
}

The Caching Hierarchy

Next.js implements aggressive caching to make RSCs fast. Understanding this hierarchy is the hardest part of the learning curve.

  1. Request Memoization: If you call getUser() in the Layout, and getUser() in the Page, Next.js dedupes it. It only runs once per request.
  2. Data Cache: The result of fetch is stored on disk on the server. It persists across requests. fetch('...', { next: { tags: ['products'] } })
  3. Full Route Cache: The rendered HTML + Flight Payload is cached at build time (Static Site Generation).
  4. Router Cache: The browser caches visited pages in memory for 30s for instant back/forward navigation.

Invalidation: When you update a product, you must call revalidateTag('products') in your Server Action. This purges Layer 2 and Layer 3 instantly.

10. Partial Prerendering (PPR)

Static (SSG) is fast but stale. Dynamic (SSR) is fresh but slow. Next.js 14 introduced PPR. It is the “Hole in the Donut” pattern. The Shell (Header, Footer, Sidebar) is pre-rendered at build time. The “Product Price” is the hole. It loads dynamically. The user gets instant HTML (The Shell), and the dynamic parts stream in 100ms later. This prevents the “White Screen of Death” during database queries.

11. Streaming and Suspense Boundaries

RSC splits the page into chunks. return <Suspense fallback={<Skeleton />}><SlowComponent /></Suspense> The server sends the HTTP header Transfer-Encoding: chunked.

  1. Chunk 1: <html>...<nav>... <Skeleton>
  2. (DB finishes)
  3. Chunk 2: <script>replace(Skeleton, RealContent)</script> This Time To First Byte (TTFB) is critical for SEO and perceived performance.

13. RSC vs Island Architecture (Astro)

Astro popularized “Islands” (Static HTML + tiny holes of interactivity). RSC is similar but different.

  • Astro: Ship 0 JS by default. Hydrate explicitly <Counter client:load />.
  • RSC: Ship 0 JS by default. Hydrate components marked 'use client'. The difference is the Router. Next.js has a Client-Side Router (SPA feel). Astro uses Multi-Page Navigation (classic feel). For E-commerce (where keeping Cart State across page loads is vital), the Next.js/RSC model is generally superior to the pure MPA model.

14. State Management in an RSC World

Where does Redux go? Nowhere. You don’t need it. If the state is “Server Data” (Products, User Profile), it lives in the Server Component (fetched per request). If the state is “UI State” (Modal Open, Accordion Expanded), it lives in useState inside a Client Component leaf. We only use Global State (Zustand/Context) for truly global client data: The Cart and Auth Token. Everything else (90% of old Redux stores) is deleted.

15. Conclusion

React Server Components are the biggest change to frontend development since the introduction of AJAX. They allow us to build “MPAs (Multi-Page Apps) with the soul of SPAs.” We get the SEO, Performance, and Simplicity of Server Rendering. We get the Rich Interactivity of React.

For large-scale e-commerce, where every millisecond of latency correlates to revenue, RSC is the only viable architecture for the next 5 years.


Migrating to Next.js?

Are you stuck on a slow Create-React-App?

Hire our Architects.