React & Modern Frontend Frameworks

Server Components & Next.js Architecture

4 min read

React Server Components (RSC) represent the biggest architectural shift in React since hooks. Understanding this model is increasingly tested in interviews, especially at companies using Next.js. Server Components are stable in React 19, and the App Router is the default in Next.js 15 (released Oct 2024) and Next.js 16 (the current release as of 2026).

The Server Components Mental Model

React now has two types of components:

Server ComponentsClient Components
Where they runServer onlyServer (SSR) + Client (hydration)
Can useasync/await, database, fsuseState, useEffect, event handlers
Cannot useHooks, browser APIs, event handlersDirect DB access, fs
Bundle impactZero — code stays on serverIncluded in JS bundle
Default in Next.js App RouterYes (default)Must add "use client"
// Server Component (default in App Router) — no directive needed
async function ProductPage({ params }) {
  // This code runs ONLY on the server
  const product = await db.products.findById(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      {/* Client Component for interactive parts */}
      <AddToCartButton productId={product.id} price={product.price} />
    </div>
  );
}

// Client Component — must have "use client" directive
'use client';

import { useState } from 'react';

function AddToCartButton({ productId, price }) {
  const [added, setAdded] = useState(false);

  return (
    <button onClick={() => {
      addToCart(productId);
      setAdded(true);
    }}>
      {added ? 'Added!' : `Add to Cart — $${price}`}
    </button>
  );
}

When to Use Each

Keep as Server Component when:

  • Fetching data from a database or API
  • Accessing backend resources (file system, environment variables)
  • Rendering static or infrequently updated content
  • The component has no interactivity

Convert to Client Component when:

  • Using React hooks (useState, useEffect, useRef, etc.)
  • Adding event handlers (onClick, onChange, onSubmit)
  • Using browser-only APIs (localStorage, window, navigator)
  • The component needs to respond to user interaction

Best practice: Push the "use client" boundary as far down the tree as possible. Keep parent layouts as Server Components and only make the interactive leaf components client-side.

Next.js App Router Patterns

Data Fetching

// Server Component: fetch directly (no useEffect needed)
async function BlogPosts() {
  const posts = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 } // ISR: revalidate every hour
  }).then(r => r.json());

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

Server Actions

// Server Action: defined with "use server"
async function createPost(formData) {
  'use server';

  const title = formData.get('title');
  const content = formData.get('content');

  await db.posts.create({ title, content });
  revalidatePath('/posts');
}

// Used in a form (Server Component)
function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">Create Post</button>
    </form>
  );
}

React 19 Actions, useOptimistic, and use()

React 19 formalized the "Action" pattern for form submissions and data mutations. Actions integrate with <form> and the new hooks useActionState, useFormStatus, and useOptimistic to handle pending states, errors, and optimistic UI without boilerplate:

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

function LikeButton({ likes, onLike }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (current) => current + 1
  );
  return (
    <form action={async () => { addOptimisticLike(); await onLike(); }}>
      <SubmitButton count={optimisticLikes} />
    </form>
  );
}

function SubmitButton({ count }) {
  const { pending } = useFormStatus();
  return <button disabled={pending}>{pending ? '...' : `${count}`}</button>;
}

The use() hook (stable in React 19) lets you read promises and Context directly during render, including inside Suspense boundaries. This simplifies data fetching in both Server and Client Components.

Streaming SSR

import { Suspense } from 'react';

// The shell renders immediately, slow parts stream in
async function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Renders immediately */}
      <Suspense fallback={<Skeleton />}>
        {/* Streams in when data is ready */}
        <RevenueChart />
      </Suspense>

      <Suspense fallback={<Skeleton />}>
        <RecentOrders />
      </Suspense>
    </div>
  );
}

Rendering Strategies

StrategyWhen Content UpdatesUse Case
SSG (Static)At build time onlyMarketing pages, docs
ISR (Incremental)Periodically (revalidate timer)Blog posts, product pages
SSR (Dynamic)Every requestPersonalized content, real-time data
Client-sideAfter hydrationHighly interactive features
// SSG: no fetch options (default)
const data = await fetch('https://api.example.com/static');

// ISR: revalidate every 60 seconds
const data = await fetch('https://api.example.com/posts', {
  next: { revalidate: 60 }
});

// SSR: fresh on every request
const data = await fetch('https://api.example.com/dashboard', {
  cache: 'no-store'
});

Common Interview Questions

Q: "Can a Server Component import a Client Component?" Yes. Server Components can render Client Components as children. The Client Component just needs the "use client" directive.

Q: "Can a Client Component import a Server Component?" Not directly. But a Client Component can accept Server Components as children via the children prop pattern.

Q: "What happens to a Server Component's code in the browser?" Nothing — it never reaches the browser. Server Component code is not included in the JavaScript bundle. This is the key performance benefit.

Interview tip: The ability to explain the RSC boundary — why certain code runs on the server vs. client, and how data flows between them — is a differentiator. Most candidates can use hooks but few can articulate the architectural model.

Now let's apply what you've learned by building a searchable data table in the next lab. :::

Quiz

Module 3: React & Modern Frontend Frameworks

Take Quiz
FREE WEEKLY NEWSLETTER

Stay on the Nerd Track

One email per week — courses, deep dives, tools, and AI experiments.

No spam. Unsubscribe anytime.