Skip to content

📅 NGÀY 51: React Server Components (RSC) - Overview

🎯 Mục tiêu học tập (5 phút)

  • [ ] Hiểu React Server Components concept ở high-level
  • [ ] Phân biệt Server Components vs Client Components
  • [ ] Nắm được benefits và trade-offs của RSC
  • [ ] Biết khi nào nên dùng Server vs Client Components
  • [ ] Hiểu RSC architecture và rendering flow (conceptual)
  • [ ] Chuẩn bị foundation cho Next.js module

🤔 Kiểm tra đầu vào (5 phút)

  1. Suspense boundaries hoạt động như thế nào với async data?
  2. Error Boundaries catch errors ở đâu trong component tree?
  3. SSR (Server-Side Rendering) khác CSR (Client-Side Rendering) như thế nào?

📖 PHẦN 1: GIỚI THIỆU KHÁI NIỆM (30 phút)

1.1 Vấn Đề Thực Tế

Traditional React = Mọi thứ chạy trên client

jsx
// ❌ Traditional React - ALL code runs on client browser
function ProductPage({ productId }) {
  const [product, setProduct] = useState(null);
  const [reviews, setReviews] = useState([]);
  const [recommendations, setRecommendations] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Client fetches everything
    Promise.all([
      fetch(`/api/products/${productId}`).then((r) => r.json()),
      fetch(`/api/reviews/${productId}`).then((r) => r.json()),
      fetch(`/api/recommendations/${productId}`).then((r) => r.json()),
    ]).then(([productData, reviewsData, recsData]) => {
      setProduct(productData);
      setReviews(reviewsData);
      setRecommendations(recsData);
      setLoading(false);
    });
  }, [productId]);

  if (loading) return <LoadingSpinner />;

  return (
    <div>
      <ProductInfo product={product} />
      <ReviewsList reviews={reviews} />
      <Recommendations items={recommendations} />
    </div>
  );
}

// Vấn đề:
// 1. 📦 Large JavaScript bundle - Cả app code gửi đến browser
// 2. ⏱️ Waterfall requests - Component mount → fetch → render
// 3. 🔄 Loading states everywhere - Poor UX
// 4. 🔍 SEO challenges - Content không có trong initial HTML
// 5. 🔐 Security risks - API keys có thể leak ra client
// 6. 💾 Duplicate code - Logic validation ở cả server & client
// 7. 🌐 Network overhead - Multiple round trips

Real-world Impact:

Typical E-commerce Page Load:
┌────────────────────────────────────────┐
│ 1. Server sends empty HTML (1KB)      │ → 50ms
│ 2. Client downloads React bundle      │ → 500ms
│ 3. Client downloads app code          │ → 800ms
│ 4. React hydrates                     │ → 200ms
│ 5. useEffect runs                     │ → 10ms
│ 6. Fetch product data                 │ → 300ms
│ 7. Fetch reviews                      │ → 250ms
│ 8. Fetch recommendations              │ → 200ms
│ 9. Final render                       │ → 50ms
└────────────────────────────────────────┘
Total: ~2.3 seconds before user sees content!

Bundle sizes:
- React: 40KB (gzipped)
- App code: 200KB (gzipped)
- Heavy libraries (charts, markdown): 100KB
→ Total: 340KB just for JavaScript

1.2 Giải Pháp: React Server Components

RSC = Components render trên server, gửi kết quả đến client

jsx
// ✅ Server Component - Runs on SERVER only
// ⚠️ CONCEPTUAL CODE - Chỉ chạy trong Next.js App Router
async function ProductPage({ productId }) {
  // NO useState! NO useEffect! NO loading states!
  // Direct data access on server
  const product = await db.products.findById(productId);
  const reviews = await db.reviews.findMany({ productId });
  const recommendations = await getRecommendations(productId);

  return (
    <div>
      <ProductInfo product={product} />
      <ReviewsList reviews={reviews} />
      <Recommendations items={recommendations} />
    </div>
  );
}

// Benefits:
// ✅ Zero JavaScript for this component
// ✅ Direct database/API access - No intermediate API layer
// ✅ Parallel data fetching - All await at once
// ✅ No loading states - Data ready when component renders
// ✅ Perfect SEO - HTML contains full content
// ✅ Smaller bundle - No data fetching code sent to client
// ✅ Better security - Secrets stay on server

Flow Comparison:

❌ TRADITIONAL REACT (Client-Side):
1. Browser requests page
2. Server sends minimal HTML + JS bundles
3. Browser downloads & parses JavaScript
4. React renders (loading state)
5. useEffect triggers API calls
6. Wait for responses
7. Re-render with data
→ Time to Interactive: 2-3 seconds

✅ REACT SERVER COMPONENTS:
1. Browser requests page
2. Server runs React components
3. Server fetches all data in parallel
4. Server renders components to RSC payload
5. Server streams HTML to browser
6. Browser shows content immediately
7. Hydrate only interactive parts
→ Time to Interactive: 0.5-1 second

1.3 Mental Model

RSC Architecture:

┌─────────────── SERVER ───────────────┐
│                                      │
│  Server Components (.server.js)     │
│  ┌────────────────────────────────┐ │
│  │ async function BlogPost()      │ │
│  │   const post = await db.get()  │ │
│  │   return <Article {...post} /> │ │
│  └────────────────────────────────┘ │
│            │                         │
│            ↓ Serialize                │
│  ┌────────────────────────────────┐ │
│  │  RSC Payload (JSON-like)       │ │
│  │  {                             │ │
│  │    type: 'article',            │ │
│  │    props: { title: '...' }     │ │
│  │  }                             │ │
│  └────────────────────────────────┘ │
└──────────────│───────────────────────┘
               │ Stream over network

┌─────────────── CLIENT ──────────────┐
│                                      │
│  Client Components (.client.js)     │
│  ┌────────────────────────────────┐ │
│  │ 'use client'                   │ │
│  │                                │ │
│  │ function LikeButton()          │ │
│  │   const [liked, setLiked] =    │ │
│  │     useState(false)             │ │
│  │   return <button onClick=...>  │ │
│  └────────────────────────────────┘ │
│            │                         │
│            ↓ Interactive              │
│  ┌────────────────────────────────┐ │
│  │  Hydrated React Tree           │ │
│  │  (Only interactive parts)      │ │
│  └────────────────────────────────┘ │
└───────────────────────────────────────┘

Component Tree Example:

jsx
// Server Component (default)
async function BlogPage() {
  const posts = await db.posts.getAll(); // Server-only

  return (
    <div>
      <Header /> {/* Server Component */}
      <Sidebar posts={posts} /> {/* Server Component */}
      {posts.map((post) => (
        // Client Component - needs interactivity
        <LikeButton
          key={post.id}
          postId={post.id}
        />
      ))}
    </div>
  );
}

// Client Component - has 'use client' directive
('use client');
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);

  return (
    <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>
  );
}

1.4 Hiểu Lầm Phổ Biến

❌ Hiểu lầm 1: "RSC = Server-Side Rendering (SSR)"

jsx
// SSR (Old approach - Next.js Pages Router):
export async function getServerSideProps() {
  const data = await fetch('...');
  return { props: { data } };
}

function Page({ data }) {
  // Component still runs on client
  // Full React bundle sent to browser
  return <div>{data.title}</div>;
}

// RSC (New approach - Next.js App Router):
async function Page() {
  const data = await fetch('...');

  // Component ONLY runs on server
  // NO JavaScript sent to client for this component
  return <div>{data.title}</div>;
}

// Differences:
// SSR: Component code sent to client + hydrated
// RSC: Component code NEVER sent to client

Comparison:

FeatureSSRRSC
Where code runsServer + ClientServer only
JavaScript sentFull component codeZero (for server components)
Can use useState✅ Yes❌ No
Can use useEffect✅ Yes❌ No
Can access database❌ No (only in getServerSideProps)✅ Yes (directly)
Bundle size impactLarge (all code)Small (only client components)

❌ Hiểu lầm 2: "Mọi component phải là Server Component"

jsx
// ❌ WRONG: Cannot use hooks in Server Component
async function ProductPage() {
  const [count, setCount] = useState(0); // ❌ Error!

  return <button onClick={() => setCount((c) => c + 1)}>Count</button>;
}

// ✅ RIGHT: Use Client Component cho interactivity
('use client');
function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>;
}

// Server Component can render Client Component
async function ProductPage() {
  const product = await db.products.get();

  return (
    <div>
      <h1>{product.name}</h1>
      <Counter /> {/* Client Component inside Server Component */}
    </div>
  );
}

❌ Hiểu lầm 3: "Client Components không thể nhận Server Component props"

jsx
// ❌ WRONG: Cannot pass Server Component as children
'use client';
function ClientWrapper({ children }) {
  const [show, setShow] = useState(true);

  return show ? children : null;
}

async function Page() {
  const data = await db.get();

  return (
    <ClientWrapper>
      {/* ❌ Server Component as children won't work */}
      <ServerComponent data={data} />
    </ClientWrapper>
  );
}

// ✅ RIGHT: Pass data as props, not components
('use client');
function ClientWrapper({ data, children }) {
  const [show, setShow] = useState(true);

  return show ? <div>{data.title}</div> : null;
}

async function Page() {
  const data = await db.get();

  return <ClientWrapper data={data} />;
}

💻 PHẦN 2: CONCEPTUAL EXAMPLES (45 phút)

Demo 1: Server vs Client Components ⭐

jsx
/**
 * Demo: Understanding Server vs Client Components
 *
 * ⚠️ CONCEPTUAL CODE - Không chạy được trong React thuần
 * Chỉ chạy trong Next.js 13+ App Router
 *
 * Mục đích: Hiểu difference và khi nào dùng cái nào
 */

// ============= SERVER COMPONENT (Default) =============
// File: app/blog/page.js
// NO 'use client' directive = Server Component

async function BlogPage() {
  // ✅ CAN: Direct database access
  const posts = await prisma.post.findMany({
    include: { author: true, comments: true },
  });

  // ✅ CAN: Use server-only libraries
  const fs = require('fs');
  const markdown = require('marked');

  // ✅ CAN: Access environment variables safely
  const apiKey = process.env.SECRET_API_KEY;

  // ✅ CAN: Complex data transformations (zero cost to client)
  const processedPosts = posts.map((post) => ({
    ...post,
    content: markdown.parse(post.content),
    readingTime: calculateReadingTime(post.content),
  }));

  // ❌ CANNOT: Use hooks
  // const [count, setCount] = useState(0); // Error!

  // ❌ CANNOT: Use browser APIs
  // window.localStorage.getItem('key'); // Error!

  // ❌ CANNOT: Add event handlers
  // <button onClick={...}> // Error!

  return (
    <div>
      <h1>Blog Posts</h1>
      {processedPosts.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>By {post.author.name}</p>
          <div dangerouslySetInnerHTML={{ __html: post.content }} />

          {/* Client Component for interactivity */}
          <LikeButton
            postId={post.id}
            initialLikes={post.likes}
          />
        </article>
      ))}
    </div>
  );
}

// ============= CLIENT COMPONENT =============
// File: app/components/LikeButton.js
('use client'); // ← This directive makes it a Client Component

import { useState } from 'react';

function LikeButton({ postId, initialLikes }) {
  // ✅ CAN: Use all React hooks
  const [likes, setLikes] = useState(initialLikes);
  const [isLiked, setIsLiked] = useState(false);

  // ✅ CAN: Use browser APIs
  const handleLike = () => {
    setIsLiked(!isLiked);
    setLikes(isLiked ? likes - 1 : likes + 1);

    // Save to localStorage
    localStorage.setItem(`liked-${postId}`, !isLiked);

    // Call API
    fetch(`/api/posts/${postId}/like`, { method: 'POST' });
  };

  // ✅ CAN: Use event handlers
  return (
    <button
      onClick={handleLike}
      className={isLiked ? 'liked' : ''}
    >
      {isLiked ? '❤️' : '🤍'} {likes}
    </button>
  );
}

// ============= DECISION MATRIX =============

/*
USE SERVER COMPONENT when:
✅ Fetching data
✅ Accessing backend resources (database, filesystem)
✅ Keeping sensitive information on server (API keys)
✅ Reducing client bundle size
✅ Heavy computations (markdown parsing, image processing)

USE CLIENT COMPONENT when:
✅ Need interactivity (onClick, onChange)
✅ Use React hooks (useState, useEffect, useContext)
✅ Use browser-only APIs (localStorage, window)
✅ Use React features (Error Boundaries with hooks - future)
✅ Need lifecycle methods
*/

// Kết quả:
// - Server Component: Chạy server, zero JS to client
// - Client Component: Chạy client, full JS sent
// - Mix and match based on needs

Demo 2: Data Fetching Patterns ⭐⭐

jsx
/**
 * Demo: Data Fetching - Traditional vs RSC
 *
 * So sánh cách fetch data
 */

// ============= TRADITIONAL REACT (Client-Side) =============

function TraditionalProductPage({ productId }) {
  const [product, setProduct] = useState(null);
  const [reviews, setReviews] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Waterfall: One after another
    async function fetchData() {
      try {
        setLoading(true);

        // Request 1
        const productRes = await fetch(`/api/products/${productId}`);
        const productData = await productRes.json();
        setProduct(productData);

        // Request 2 (depends on product)
        const reviewsRes = await fetch(`/api/reviews/${productId}`);
        const reviewsData = await reviewsRes.json();
        setReviews(reviewsData);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [productId]);

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;

  return (
    <div>
      <h1>{product.name}</h1>
      <p>${product.price}</p>
      <ReviewsList reviews={reviews} />
    </div>
  );
}

// Problems:
// 1. Waterfall: product → reviews (sequential)
// 2. Loading states management
// 3. Error handling boilerplate
// 4. JavaScript bundle includes all fetch logic
// 5. API layer needed (/api/products, /api/reviews)

// ============= RSC APPROACH =============

// ⚠️ CONCEPTUAL - Only works in Next.js App Router
async function RSCProductPage({ productId }) {
  // Parallel fetching - both at once!
  const [product, reviews] = await Promise.all([
    db.products.findById(productId),
    db.reviews.findMany({ where: { productId } }),
  ]);

  // Direct database access - no API layer needed
  // No loading states - data ready before render
  // No useState/useEffect - simpler code

  return (
    <div>
      <h1>{product.name}</h1>
      <p>${product.price}</p>
      <ReviewsList reviews={reviews} />
    </div>
  );
}

// Benefits:
// ✅ Parallel fetching (faster)
// ✅ No loading states (simpler)
// ✅ Direct DB access (no API layer)
// ✅ Zero fetch code sent to client
// ✅ Better error handling (Error Boundaries)

// ============= STREAMING PATTERN =============

// Traditional: Wait for ALL data before showing anything
async function TraditionalDashboard() {
  const [user, analytics, notifications] = await Promise.all([
    fetchUser(),
    fetchAnalytics(), // Takes 2 seconds
    fetchNotifications(),
  ]);

  // User waits 2 seconds before seeing ANYTHING
  return (
    <div>
      <UserWidget user={user} />
      <AnalyticsWidget data={analytics} />
      <NotificationsWidget data={notifications} />
    </div>
  );
}

// RSC: Stream components as data arrives
async function StreamingDashboard() {
  const userPromise = fetchUser();
  const analyticsPromise = fetchAnalytics();
  const notificationsPromise = fetchNotifications();

  return (
    <div>
      {/* Show immediately */}
      <Suspense fallback={<UserSkeleton />}>
        <UserWidget promise={userPromise} />
      </Suspense>

      {/* Show when ready (2s) */}
      <Suspense fallback={<AnalyticsSkeleton />}>
        <AnalyticsWidget promise={analyticsPromise} />
      </Suspense>

      {/* Show when ready */}
      <Suspense fallback={<NotificationsSkeleton />}>
        <NotificationsWidget promise={notificationsPromise} />
      </Suspense>
    </div>
  );
}

// Flow:
// 0ms: User sees UserSkeleton + AnalyticsSkeleton + NotificationsSkeleton
// 100ms: UserWidget shows (user data ready)
// 150ms: NotificationsWidget shows
// 2000ms: AnalyticsWidget shows
// → Better perceived performance!

// Kết quả:
// Traditional: Waterfall, slow, complex
// RSC: Parallel, fast, simple
// Streaming: Progressive rendering, best UX

Demo 3: Composition Patterns ⭐⭐⭐

jsx
/**
 * Demo: Server & Client Component Composition
 *
 * Rules:
 * 1. Server Component can import Client Component ✅
 * 2. Server Component can import Server Component ✅
 * 3. Client Component can import Client Component ✅
 * 4. Client Component CANNOT import Server Component ❌
 *    (but can receive as children prop)
 */

// ============= PATTERN 1: Server → Client ✅ =============

// Server Component
async function ProductPage({ productId }) {
  const product = await db.products.get(productId);

  return (
    <div>
      <h1>{product.name}</h1>

      {/* ✅ Server Component can use Client Component */}
      <AddToCartButton product={product} />
    </div>
  );
}

// Client Component
('use client');
function AddToCartButton({ product }) {
  const [isAdding, setIsAdding] = useState(false);

  const handleAdd = async () => {
    setIsAdding(true);
    await addToCart(product.id);
    setIsAdding(false);
  };

  return (
    <button
      onClick={handleAdd}
      disabled={isAdding}
    >
      {isAdding ? 'Adding...' : 'Add to Cart'}
    </button>
  );
}

// ============= PATTERN 2: Server → Server ✅ =============

// Parent Server Component
async function BlogPage() {
  const posts = await db.posts.getAll();

  return (
    <div>
      {posts.map((post) => (
        // ✅ Server Component can use Server Component
        <BlogPostCard
          key={post.id}
          post={post}
        />
      ))}
    </div>
  );
}

// Child Server Component
async function BlogPostCard({ post }) {
  const author = await db.users.get(post.authorId);

  return (
    <article>
      <h2>{post.title}</h2>
      <p>By {author.name}</p>
    </article>
  );
}

// ============= PATTERN 3: Client → Client ✅ =============

// Parent Client Component
('use client');
function ShoppingCart() {
  const [items, setItems] = useState([]);

  return (
    <div>
      {items.map((item) => (
        // ✅ Client Component can use Client Component
        <CartItem
          key={item.id}
          item={item}
          onRemove={() => removeItem(item.id)}
        />
      ))}
    </div>
  );
}

// Child Client Component
('use client');
function CartItem({ item, onRemove }) {
  return (
    <div>
      <span>{item.name}</span>
      <button onClick={onRemove}>Remove</button>
    </div>
  );
}

// ============= PATTERN 4: Client → Server ❌ (Direct Import) =============

// ❌ WRONG: Client Component cannot import Server Component
('use client');
import ServerComponent from './ServerComponent'; // ❌ Error!

function ClientWrapper() {
  return <ServerComponent />; // Won't work
}

// ============= PATTERN 5: Client → Server ✅ (via children) =============

// ✅ RIGHT: Pass Server Component as children
('use client');
function ClientWrapper({ children }) {
  const [show, setShow] = useState(true);

  return (
    <div>
      <button onClick={() => setShow(!show)}>Toggle</button>
      {show && children}
    </div>
  );
}

// Server Component can pass Server Component as children
async function Page() {
  const data = await db.get();

  return (
    <ClientWrapper>
      {/* ✅ Server Component passed as children */}
      <ServerDataDisplay data={data} />
    </ClientWrapper>
  );
}

async function ServerDataDisplay({ data }) {
  return <div>{data.title}</div>;
}

// ============= BEST PRACTICE: Leaf Client Components =============

// ❌ BAD: Entire tree becomes Client Component
('use client');
function Layout({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <div className={theme}>
      <Header /> {/* Now a Client Component */}
      <Sidebar /> {/* Now a Client Component */}
      <main>{children}</main> {/* All children are Client Components */}
      <Footer /> {/* Now a Client Component */}
    </div>
  );
}

// ✅ GOOD: Keep Client Components at the leaves
function Layout({ children }) {
  // Server Component (default)
  return (
    <div>
      <Header /> {/* Server Component */}
      <Sidebar /> {/* Server Component */}
      <main>
        {children} {/* Can be Server or Client */}
        {/* Only interactive part is Client */}
        <ThemeToggle /> {/* Client Component */}
      </main>
      <Footer /> {/* Server Component */}
    </div>
  );
}

('use client');
function ThemeToggle() {
  const [theme, setTheme] = useState('light');
  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Toggle Theme
    </button>
  );
}

// Kết quả:
// ✅ Most of tree = Server Components (zero JS)
// ✅ Only leaves = Client Components (minimal JS)
// ✅ Better performance, smaller bundle

🔨 PHẦN 3: CONCEPTUAL EXERCISES (60 phút)

⭐ Level 1: Identify Server vs Client (15 phút)

jsx
/**
 * 🎯 Mục tiêu: Nhận biết khi nào dùng Server vs Client Component
 * ⏱️ Thời gian: 15 phút
 * 🚫 KHÔNG code: Chỉ phân tích và quyết định
 *
 * Requirements:
 * 1. Đọc từng component
 * 2. Quyết định: Server hay Client?
 * 3. Giải thích lý do
 */

// Component 1
function UserProfile({ userId }) {
  const user = await db.users.findById(userId);

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
// ❓ Server hay Client? Tại sao?

// Component 2
function LikeButton({ postId }) {
  const [liked, setLiked] = useState(false);

  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}
// ❓ Server hay Client? Tại sao?

// Component 3
function ProductList({ category }) {
  const products = await fetch(`/api/products?category=${category}`);

  return (
    <div>
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </div>
  );
}
// ❓ Server hay Client? Tại sao?

// Component 4
function SearchBox() {
  const [query, setQuery] = useState('');
  const router = useRouter();

  const handleSearch = () => {
    router.push(`/search?q=${query}`);
  };

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
    />
  );
}
// ❓ Server hay Client? Tại sao?

// Component 5
function BlogPost({ slug }) {
  const post = await db.posts.findOne({ slug });
  const html = await markdownToHtml(post.content);

  return <article dangerouslySetInnerHTML={{ __html: html }} />;
}
// ❓ Server hay Client? Tại sao?
💡 Solution
jsx
/**
 * Phân tích từng component
 */

// Component 1: SERVER COMPONENT ✅
// Lý do:
// - Fetches data (await db.users)
// - No interactivity
// - No hooks needed
// - Direct database access
// Benefits: Zero JS to client, fast data access

// Component 2: CLIENT COMPONENT ✅
// Lý do:
// - Uses useState hook
// - Needs interactivity (onClick)
// - Manages local state
// Must add: 'use client' directive

// Component 3: SERVER COMPONENT ✅
// Lý do:
// - Fetches data (await fetch)
// - No interactivity
// - Pure rendering based on data
// Benefits: Data fetched on server, smaller bundle

// Component 4: CLIENT COMPONENT ✅
// Lý do:
// - Uses useState
// - Uses useRouter (browser API)
// - Event handlers (onChange, onKeyPress)
// - Form interactivity
// Must add: 'use client' directive

// Component 5: SERVER COMPONENT ✅
// Lý do:
// - Fetches from database
// - Heavy computation (markdownToHtml) better on server
// - No interactivity
// - Static content rendering
// Benefits: Markdown processing zero cost to client

// 💡 RULE OF THUMB:
// Server: Data fetching, heavy computation, static content
// Client: Interactivity, hooks, browser APIs, state management

⭐⭐ Level 2: Convert Traditional to RSC (25 phút)

jsx
/**
 * 🎯 Mục tiêu: Convert traditional React sang RSC architecture
 * ⏱️ Thời gian: 25 phút
 *
 * Scenario: Dashboard với user info, stats, và notifications
 *
 * Task:
 * 1. Identify parts that should be Server Components
 * 2. Identify parts that should be Client Components
 * 3. Rewrite với RSC patterns
 * 4. Document reasoning
 */

// ❌ TRADITIONAL REACT
function Dashboard() {
  const [user, setUser] = useState(null);
  const [stats, setStats] = useState(null);
  const [notifications, setNotifications] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    Promise.all([
      fetch('/api/user').then((r) => r.json()),
      fetch('/api/stats').then((r) => r.json()),
      fetch('/api/notifications').then((r) => r.json()),
    ]).then(([userData, statsData, notifData]) => {
      setUser(userData);
      setStats(statsData);
      setNotifications(notifData);
      setLoading(false);
    });
  }, []);

  if (loading) return <Spinner />;

  return (
    <div>
      <UserInfo user={user} />
      <Stats data={stats} />
      <NotificationsList notifications={notifications} />
    </div>
  );
}

function NotificationsList({ notifications }) {
  const [filter, setFilter] = useState('all');

  const filtered = notifications.filter(
    (n) => filter === 'all' || n.type === filter,
  );

  return (
    <div>
      <select
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      >
        <option value='all'>All</option>
        <option value='alert'>Alerts</option>
        <option value='message'>Messages</option>
      </select>
      {filtered.map((n) => (
        <div key={n.id}>{n.message}</div>
      ))}
    </div>
  );
}

// TODO: Rewrite với RSC architecture
// TODO: Document which parts are Server vs Client
// TODO: Explain benefits of new architecture
💡 Solution
jsx
/**
 * RSC Architecture - Converted
 */

// ============= SERVER COMPONENTS =============

// Main Dashboard - Server Component
async function Dashboard() {
  // Parallel data fetching on server
  const [user, stats, notifications] = await Promise.all([
    db.users.getCurrent(),
    db.stats.getOverview(),
    db.notifications.getRecent(),
  ]);

  return (
    <div>
      {/* Server Component - static display */}
      <UserInfo user={user} />

      {/* Server Component - static display */}
      <Stats data={stats} />

      {/* Client Component - needs interactivity for filtering */}
      <NotificationsList initialNotifications={notifications} />
    </div>
  );
}

// Server Component - No hooks needed
function UserInfo({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// Server Component - No hooks needed
function Stats({ data }) {
  return (
    <div>
      <div>Total Users: {data.totalUsers}</div>
      <div>Active: {data.activeUsers}</div>
    </div>
  );
}

// ============= CLIENT COMPONENT =============

// Client Component - needs state for filtering
('use client');
import { useState } from 'react';

function NotificationsList({ initialNotifications }) {
  const [filter, setFilter] = useState('all');

  const filtered = initialNotifications.filter(
    (n) => filter === 'all' || n.type === filter,
  );

  return (
    <div>
      <select
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      >
        <option value='all'>All</option>
        <option value='alert'>Alerts</option>
        <option value='message'>Messages</option>
      </select>
      {filtered.map((n) => (
        <div key={n.id}>{n.message}</div>
      ))}
    </div>
  );
}

// ============= ARCHITECTURE DECISIONS =============

/*
BREAKDOWN:
┌─────────────────────────────────────────────┐
│ Dashboard (SERVER)                          │
│ - Fetches all data in parallel             │
│ - No useState/useEffect needed             │
│ - Direct database access                   │
│                                             │
│ ┌─────────────────────────────────────────┐ │
│ │ UserInfo (SERVER)                       │ │
│ │ - Pure display                          │ │
│ │ - Zero JS to client                     │ │
│ └─────────────────────────────────────────┘ │
│                                             │
│ ┌─────────────────────────────────────────┐ │
│ │ Stats (SERVER)                          │ │
│ │ - Pure display                          │ │
│ │ - Zero JS to client                     │ │
│ └─────────────────────────────────────────┘ │
│                                             │
│ ┌─────────────────────────────────────────┐ │
│ │ NotificationsList (CLIENT)              │ │
│ │ - Needs useState for filter             │ │
│ │ - Interactive dropdown                  │ │
│ │ - Only this sends JS to client          │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

BENEFITS:
✅ Faster initial load (parallel fetching on server)
✅ No loading states needed
✅ Smaller bundle (only NotificationsList is client JS)
✅ Better SEO (all content in HTML)
✅ Direct database access (no API layer)

BUNDLE SIZE COMPARISON:
Traditional:
- Dashboard code: 5KB
- UserInfo code: 2KB
- Stats code: 2KB
- NotificationsList code: 3KB
- Data fetching logic: 4KB
- React hooks overhead: 2KB
Total: 18KB

RSC:
- NotificationsList code: 3KB
- Data already in HTML: 0KB
Total: 3KB (83% reduction!)
*/

⭐⭐⭐ Level 3: Design RSC Architecture (40 phút)

jsx
/**
 * 🎯 Mục tiêu: Design complete RSC architecture
 * ⏱️ Thời gian: 40 phút
 *
 * 📋 Scenario: E-commerce Product Page
 *
 * Requirements:
 * - Product details (name, price, description, images)
 * - Reviews list (rating, comment, author)
 * - Recommendations (related products)
 * - Add to cart button (interactive)
 * - Quantity selector (interactive)
 * - Review form (interactive)
 * - Breadcrumbs (navigation)
 *
 * Tasks:
 * 1. Design component tree (which are Server, which are Client)
 * 2. Document data fetching strategy
 * 3. Identify benefits vs traditional approach
 * 4. List potential issues and solutions
 */

// TODO: Design component architecture
// TODO: Specify Server vs Client for each component
// TODO: Document data flow
// TODO: Compare bundle sizes (traditional vs RSC)
💡 Solution
jsx
/**
 * E-commerce Product Page - RSC Architecture Design
 */

// ============= COMPONENT TREE =============

/*
ProductPage (SERVER)
├── Breadcrumbs (SERVER)
├── ProductDetails (SERVER)
│   ├── ImageGallery (SERVER)
│   ├── ProductInfo (SERVER)
│   └── PurchaseSection (CLIENT) ← Interactive
│       ├── QuantitySelector (CLIENT)
│       └── AddToCartButton (CLIENT)
├── ReviewsSection (MIXED)
│   ├── ReviewsSummary (SERVER)
│   ├── ReviewsList (SERVER)
│   └── ReviewForm (CLIENT) ← Interactive
└── Recommendations (SERVER)
    └── ProductCard (SERVER)
        └── QuickAddButton (CLIENT) ← Interactive
*/

// ============= IMPLEMENTATION =============

// ─────── SERVER COMPONENT (Main Page) ───────
async function ProductPage({ productId }) {
  // Parallel data fetching
  const [product, reviews, recommendations] = await Promise.all([
    db.products.findById(productId),
    db.reviews.findMany({ productId }),
    db.products.findRelated(productId),
  ]);

  return (
    <div>
      <Breadcrumbs
        category={product.category}
        productName={product.name}
      />

      <ProductDetails
        product={product}
        averageRating={calculateAverage(reviews)}
      />

      <ReviewsSection
        reviews={reviews}
        productId={productId}
      />

      <Recommendations products={recommendations} />
    </div>
  );
}

// ─────── SERVER COMPONENT ───────
function Breadcrumbs({ category, productName }) {
  return (
    <nav>
      Home / {category} / {productName}
    </nav>
  );
}

// ─────── SERVER COMPONENT ───────
function ProductDetails({ product, averageRating }) {
  return (
    <div>
      <ImageGallery images={product.images} />

      <div>
        <h1>{product.name}</h1>
        <div>
          ⭐ {averageRating} ({product.reviewCount} reviews)
        </div>
        <p>{product.description}</p>
        <div>${product.price}</div>

        {/* Client Component for interactivity */}
        <PurchaseSection
          productId={product.id}
          price={product.price}
          inStock={product.inStock}
        />
      </div>
    </div>
  );
}

// ─────── SERVER COMPONENT ───────
function ImageGallery({ images }) {
  return (
    <div>
      {images.map((img, idx) => (
        <img
          key={idx}
          src={img.url}
          alt={img.alt}
        />
      ))}
    </div>
  );
}

// ─────── CLIENT COMPONENT ───────
('use client');
function PurchaseSection({ productId, price, inStock }) {
  const [quantity, setQuantity] = useState(1);
  const [isAdding, setIsAdding] = useState(false);

  const handleAddToCart = async () => {
    setIsAdding(true);
    await addToCart(productId, quantity);
    setIsAdding(false);
  };

  return (
    <div>
      <QuantitySelector
        value={quantity}
        onChange={setQuantity}
        max={inStock}
      />

      <button
        onClick={handleAddToCart}
        disabled={!inStock || isAdding}
      >
        {isAdding ? 'Adding...' : `Add to Cart - $${price * quantity}`}
      </button>

      <p>{inStock} in stock</p>
    </div>
  );
}

// ─────── CLIENT COMPONENT ───────
('use client');
function QuantitySelector({ value, onChange, max }) {
  return (
    <div>
      <button onClick={() => onChange(Math.max(1, value - 1))}>-</button>
      <span>{value}</span>
      <button onClick={() => onChange(Math.min(max, value + 1))}>+</button>
    </div>
  );
}

// ─────── SERVER COMPONENT ───────
function ReviewsSection({ reviews, productId }) {
  const stats = calculateReviewStats(reviews);

  return (
    <div>
      <ReviewsSummary stats={stats} />
      <ReviewsList reviews={reviews} />
      <ReviewForm productId={productId} />
    </div>
  );
}

// ─────── SERVER COMPONENT ───────
function ReviewsSummary({ stats }) {
  return (
    <div>
      <h3>Customer Reviews</h3>
      <div>Average: {stats.average}⭐</div>
      <div>
        5★: {stats.fiveStar} | 4★: {stats.fourStar} ...
      </div>
    </div>
  );
}

// ─────── SERVER COMPONENT ───────
function ReviewsList({ reviews }) {
  return (
    <div>
      {reviews.map((review) => (
        <div key={review.id}>
          <div>{'⭐'.repeat(review.rating)}</div>
          <p>{review.comment}</p>
          <span>
            By {review.author} on {review.date}
          </span>
        </div>
      ))}
    </div>
  );
}

// ─────── CLIENT COMPONENT ───────
('use client');
function ReviewForm({ productId }) {
  const [rating, setRating] = useState(5);
  const [comment, setComment] = useState('');
  const [submitting, setSubmitting] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setSubmitting(true);
    await submitReview(productId, { rating, comment });
    setSubmitting(false);
    setComment('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <select
        value={rating}
        onChange={(e) => setRating(+e.target.value)}
      >
        <option value={5}>5 Stars</option>
        <option value={4}>4 Stars</option>
        <option value={3}>3 Stars</option>
        <option value={2}>2 Stars</option>
        <option value={1}>1 Star</option>
      </select>

      <textarea
        value={comment}
        onChange={(e) => setComment(e.target.value)}
        placeholder='Write your review...'
      />

      <button
        type='submit'
        disabled={submitting}
      >
        {submitting ? 'Submitting...' : 'Submit Review'}
      </button>
    </form>
  );
}

// ─────── SERVER COMPONENT ───────
function Recommendations({ products }) {
  return (
    <div>
      <h3>You Might Also Like</h3>
      <div>
        {products.map((product) => (
          <ProductCard
            key={product.id}
            product={product}
          />
        ))}
      </div>
    </div>
  );
}

// ─────── SERVER COMPONENT ───────
function ProductCard({ product }) {
  return (
    <div>
      <img
        src={product.image}
        alt={product.name}
      />
      <h4>{product.name}</h4>
      <p>${product.price}</p>
      <QuickAddButton productId={product.id} />
    </div>
  );
}

// ─────── CLIENT COMPONENT ───────
('use client');
function QuickAddButton({ productId }) {
  const [isAdding, setIsAdding] = useState(false);

  const handleQuickAdd = async () => {
    setIsAdding(true);
    await addToCart(productId, 1);
    setIsAdding(false);
  };

  return (
    <button
      onClick={handleQuickAdd}
      disabled={isAdding}
    >
      {isAdding ? '...' : 'Quick Add'}
    </button>
  );
}

// ============= BENEFITS ANALYSIS =============

/*
BUNDLE SIZE COMPARISON:

Traditional React (Client-Side):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Component                    | Size
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ProductPage                  | 8KB
Breadcrumbs                  | 2KB
ProductDetails               | 6KB
ImageGallery                 | 4KB
PurchaseSection              | 5KB
QuantitySelector             | 2KB
ReviewsSection               | 8KB
ReviewsList                  | 4KB
ReviewForm                   | 6KB
Recommendations              | 5KB
ProductCard                  | 3KB
QuickAddButton               | 2KB
Data fetching logic          | 10KB
State management             | 8KB
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TOTAL                        | 73KB
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

RSC Architecture:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Component                    | Size
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ProductPage (Server)         | 0KB ✅
Breadcrumbs (Server)         | 0KB ✅
ProductDetails (Server)      | 0KB ✅
ImageGallery (Server)        | 0KB ✅
PurchaseSection (Client)     | 5KB
QuantitySelector (Client)    | 2KB
ReviewsSection (Server)      | 0KB ✅
ReviewsSummary (Server)      | 0KB ✅
ReviewsList (Server)         | 0KB ✅
ReviewForm (Client)          | 6KB
Recommendations (Server)     | 0KB ✅
ProductCard (Server)         | 0KB ✅
QuickAddButton (Client)      | 2KB
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TOTAL                        | 15KB
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

SAVINGS: 73KB → 15KB (79% reduction!)

PERFORMANCE METRICS:

Traditional:
- Time to Interactive: ~3.2s
- First Contentful Paint: ~1.8s
- Largest Contentful Paint: ~2.5s
- Total Blocking Time: ~800ms

RSC:
- Time to Interactive: ~0.8s ✅ (75% faster)
- First Contentful Paint: ~0.4s ✅ (78% faster)
- Largest Contentful Paint: ~0.6s ✅ (76% faster)
- Total Blocking Time: ~200ms ✅ (75% less)

SEO:
Traditional:
- Initial HTML: Empty (JavaScript required)
- Search engines: Must run JS to see content
- Crawl budget: Wasted on JS execution

RSC:
- Initial HTML: Full content
- Search engines: Immediate content access
- Crawl budget: Efficient

DEVELOPER EXPERIENCE:

Traditional:
- ❌ Complex state management
- ❌ Loading states everywhere
- ❌ Error handling boilerplate
- ❌ API layer needed
- ❌ Waterfall requests

RSC:
- ✅ Simple async/await
- ✅ No loading states
- ✅ Error boundaries handle errors
- ✅ Direct database access
- ✅ Parallel requests
*/

// ============= POTENTIAL ISSUES & SOLUTIONS =============

/*
ISSUE 1: Cannot use hooks in Server Components
Solution: Move interactive parts to Client Components

ISSUE 2: Cannot import Server Components in Client Components
Solution: Pass Server Components as children props

ISSUE 3: Large initial HTML payload
Solution: Use Streaming + Suspense for progressive loading

ISSUE 4: Serialization limits (functions, classes)
Solution: Only pass serializable data as props

ISSUE 5: Learning curve
Solution: Start with small features, gradually adopt
*/

📊 PHẦN 4: SO SÁNH PATTERNS (30 phút)

Bảng So Sánh Trade-offs

AspectTraditional React (CSR)SSR (Next.js Pages)RSC (Next.js App Router)
Rendering LocationClient onlyServer + ClientServer (default) + Client (opt-in)
JavaScript BundleLarge (all code)Large (all code)Small (only Client Components)
Data FetchingClient (useEffect)getServerSidePropsDirect in component (async)
SEOPoor (needs JS)Good (HTML ready)Excellent (HTML + streaming)
Time to InteractiveSlow (2-3s)Medium (1-2s)Fast (0.5-1s)
Can use hooks✅ Yes✅ Yes⚠️ Only in Client Components
Direct DB access❌ No⚠️ Only in getServerSideProps✅ Yes (in Server Components)
Bundle size~300-500KB~300-500KB~50-100KB
Loading statesRequiredRequiredOptional (use Suspense)
ComplexityMediumHighMedium-High (new concepts)
PerformancePoorGoodExcellent
When to useSimple SPAsSEO-critical pagesModern apps, best performance

Decision Tree

START: Building a React feature?
    |
    ├─ Need interactivity (onClick, useState, forms)?
    │   |
    │   ├─ Yes → CLIENT COMPONENT ('use client')
    │   │   Examples:
    │   │   - Buttons with click handlers
    │   │   - Forms with controlled inputs
    │   │   - Interactive widgets (dropdowns, modals)
    │   │   - Browser API usage (localStorage, geolocation)
    │   │
    │   └─ No → Continue to next question

    ├─ Need to fetch data?
    │   |
    │   ├─ Yes → SERVER COMPONENT (default)
    │   │   Examples:
    │   │   - Blog post content
    │   │   - Product details
    │   │   - User profiles
    │   │   - Dashboard data
    │   │
    │   └─ No → Continue to next question

    ├─ Heavy computation (markdown, image processing)?
    │   |
    │   ├─ Yes → SERVER COMPONENT
    │   │   Benefits:
    │   │   - Computation happens on server
    │   │   - Zero cost to client bundle
    │   │   - Faster page loads
    │   │
    │   └─ No → Continue to next question

    ├─ Static content display only?
    │   |
    │   ├─ Yes → SERVER COMPONENT (default)
    │   │   Examples:
    │   │   - Headers, footers
    │   │   - Static text blocks
    │   │   - Non-interactive cards
    │   │
    │   └─ No → Analyze specific needs

    └─ MIXED: Use Server Component as wrapper,
       Client Components for interactive parts

Composition Patterns:

Pattern 1: Server wraps Client
✅ GOOD
async function Page() {
  const data = await fetchData();
  return (
    <>
      <ServerHeader data={data} />
      <ClientInteractive data={data} />
      <ServerFooter />
    </>
  );
}

Pattern 2: Client wraps Server (via children)
✅ GOOD (when necessary)
'use client';
function ClientWrapper({ children }) {
  return <div className={useTheme()}>{children}</div>;
}

// Usage
<ClientWrapper>
  <ServerContent />
</ClientWrapper>

Pattern 3: Client imports Server
❌ BAD - Will cause error
'use client';
import ServerComponent from './server';

function Client() {
  return <ServerComponent />; // Error!
}

Pattern 4: Server imports Client
✅ GOOD - Recommended
async function Server() {
  const data = await fetchData();
  return <ClientButton data={data} />;
}

🧪 PHẦN 5: DEBUG LAB (20 phút)

Bug 1: Using Hooks in Server Component

jsx
/**
 * 🐛 BUG: Cannot use hooks in Server Component
 */

// File: app/posts/page.js
async function PostsPage() {
  const [filter, setFilter] = useState('all'); // ❌ Error!

  const posts = await db.posts.findMany();

  return (
    <div>
      <select
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      >
        <option>All</option>
        <option>Published</option>
      </select>
      {posts.map((post) => (
        <Post
          key={post.id}
          post={post}
        />
      ))}
    </div>
  );
}

// ❓ TẠI SAO: Error "useState is not defined"?
// ❓ LÀM SAO FIX?
💡 Giải thích & Fix
jsx
/**
 * GIẢI THÍCH:
 * Server Components không support hooks (useState, useEffect, etc.)
 * Server Components chỉ chạy trên server, không có client-side lifecycle
 *
 * LÝ DO:
 * - Server Components render một lần trên server
 * - Không có re-renders
 * - Không có interactivity
 * - Hooks require client-side React runtime
 */

// ✅ FIX: Extract interactive part to Client Component

// File: app/posts/page.js (Server Component)
async function PostsPage() {
  const posts = await db.posts.findMany();

  return <PostsListWithFilter initialPosts={posts} />;
}

// File: app/components/PostsListWithFilter.js (Client Component)
('use client');
import { useState } from 'react';

function PostsListWithFilter({ initialPosts }) {
  const [filter, setFilter] = useState('all');

  const filtered = initialPosts.filter(
    (post) => filter === 'all' || post.status === filter,
  );

  return (
    <div>
      <select
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      >
        <option value='all'>All</option>
        <option value='published'>Published</option>
        <option value='draft'>Draft</option>
      </select>

      {filtered.map((post) => (
        <Post
          key={post.id}
          post={post}
        />
      ))}
    </div>
  );
}

// 💡 PATTERN:
// Server Component: Fetch data
// Client Component: Handle interactivity
// Pass data from Server to Client via props

Bug 2: Importing Server Component in Client Component

jsx
/**
 * 🐛 BUG: Cannot import Server Component trong Client Component
 */

// File: app/components/ServerUserCard.js (Server Component)
async function ServerUserCard({ userId }) {
  const user = await db.users.findById(userId);

  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// File: app/components/UserModal.js (Client Component)
('use client');
import { useState } from 'react';
import ServerUserCard from './ServerUserCard'; // ❌ Error!

function UserModal({ userId }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>View User</button>

      {isOpen && (
        <div>
          <ServerUserCard userId={userId} /> {/* ❌ Won't work */}
        </div>
      )}
    </>
  );
}

// ❓ VẤN ĐỀ: "You're importing a component that needs server-side..."
// ❓ FIX?
💡 Giải thích & Fix
jsx
/**
 * GIẢI THÍCH:
 * Client Components cannot import Server Components directly
 * Server Components can only be rendered on server
 * Client Components render on client
 *
 * Rule: Client → Server imports are not allowed
 * Exception: Can pass Server Components as children
 */

// ✅ FIX 1: Pass Server Component as children prop

// File: app/page.js (Server Component)
async function Page({ userId }) {
  return (
    <UserModal>
      {/* Server Component passed as children */}
      <ServerUserCard userId={userId} />
    </UserModal>
  );
}

// File: app/components/UserModal.js (Client Component)
('use client');
import { useState } from 'react';

function UserModal({ children }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>View User</button>

      {isOpen && (
        <div className='modal'>
          {children} {/* Renders Server Component */}
        </div>
      )}
    </>
  );
}

// ✅ FIX 2: Fetch data on server, pass to Client Component

// Server Component
async function Page({ userId }) {
  const user = await db.users.findById(userId);

  return <UserModal user={user} />;
}

// Client Component
('use client');
function UserModal({ user }) {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <button onClick={() => setIsOpen(true)}>View User</button>

      {isOpen && (
        <div className='modal'>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      )}
    </>
  );
}

// 💡 KEY INSIGHT:
// Data flows: Server → Client ✅
// Components flow: Server → Client ✅ (as children)
// Direct imports: Server → Client ❌ (not allowed)

Bug 3: Non-serializable Props

jsx
/**
 * 🐛 BUG: Cannot pass functions/class instances as props
 */

// Server Component
async function Page() {
  const user = await db.users.findById('123');

  const handleClick = () => {
    console.log('Clicked!');
  };

  return (
    <ClientButton
      user={user} // ✅ OK - plain object
      onClick={handleClick} // ❌ Error! Cannot serialize function
      date={new Date()} // ❌ Error! Cannot serialize Date
    />
  );
}

// ❓ VẤN ĐỀ: "Only plain objects can be passed to Client Components"
// ❓ FIX?
💡 Giải thích & Fix
jsx
/**
 * GIẢI THÍCH:
 * Props từ Server → Client phải serializable (JSON.stringify compatible)
 *
 * ✅ SERIALIZABLE:
 * - Strings, numbers, booleans
 * - Plain objects, arrays
 * - null, undefined
 *
 * ❌ NOT SERIALIZABLE:
 * - Functions
 * - Class instances (Date, Map, Set, etc.)
 * - Symbols
 * - undefined (in objects)
 */

// ✅ FIX: Convert non-serializable to serializable

// Server Component
async function Page() {
  const user = await db.users.findById('123');

  // Convert Date to string
  const createdAt = user.createdAt.toISOString();

  return (
    <ClientButton
      user={user}
      createdAt={createdAt} // ✅ String is serializable
    />
  );
}

// Client Component
('use client');
function ClientButton({ user, createdAt }) {
  // Define handler in Client Component
  const handleClick = () => {
    console.log('Clicked!', user);
  };

  // Convert string back to Date if needed
  const date = new Date(createdAt);

  return (
    <button onClick={handleClick}>Created: {date.toLocaleDateString()}</button>
  );
}

// 💡 BEST PRACTICE:
// Server Component: Prepare data (fetch, transform)
// Client Component: Handle interactions (events, state)
// Props: Only serializable data

✅ PHẦN 6: TỰ ĐÁNH GIÁ (15 phút)

Knowledge Check

  • [ ] Tôi hiểu RSC là gì và khác SSR như thế nào
  • [ ] Tôi biết phân biệt Server vs Client Components
  • [ ] Tôi hiểu khi nào dùng Server Component
  • [ ] Tôi hiểu khi nào dùng Client Component
  • [ ] Tôi biết Server Component KHÔNG dùng được hooks
  • [ ] Tôi biết Client Component KHÔNG import được Server Component
  • [ ] Tôi hiểu composition patterns (Server → Client)
  • [ ] Tôi biết benefits của RSC (bundle size, performance)
  • [ ] Tôi hiểu trade-offs của RSC
  • [ ] Tôi sẵn sàng cho Next.js module để thực hành

Conceptual Understanding Check

Scenario 1:

jsx
function Component() {
  const data = await fetch('/api/data');
  return <div>{data.title}</div>;
}

❓ Server hay Client? → Server (async, fetch data)

Scenario 2:

jsx
function Component() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

❓ Server hay Client? → Client (useState, onClick)

Scenario 3:

jsx
async function Component() {
  const posts = await db.posts.getAll();
  return posts.map((p) => (
    <ClientCard
      key={p.id}
      post={p}
    />
  ));
}

❓ Server hay Client? → Server (async, db access)

Scenario 4:

jsx
function Component({ children }) {
  const theme = useTheme();
  return <div className={theme}>{children}</div>;
}

❓ Server hay Client? → Client (useTheme hook)


🏠 BÀI TẬP VỀ NHÀ

Bắt buộc (30 phút)

Analyze và redesign một trang blog:

  • Identify tất cả components
  • Classify: Server vs Client
  • Document reasoning cho mỗi decision
  • Estimate bundle size savings
  • List potential issues

Nâng cao (60 phút)

Design RSC architecture cho complex app:

  • Multi-tenant SaaS dashboard
  • Multiple user roles
  • Real-time notifications
  • Complex data visualizations
  • Document complete component tree
  • Identify Server/Client boundaries
  • Design data fetching strategy
  • Plan error handling approach

📚 TÀI LIỆU THAM KHẢO

Bắt buộc đọc

  1. React Server Components RFC
  2. Next.js App Router Documentation

Đọc thêm

  1. Server Components - Deep Dive (Dan Abramov)
  2. React Server Components in Next.js 13

🔗 KẾT NỐI KIẾN THỨC

Kiến thức nền

  • Ngày 49: Suspense - RSC dùng Suspense for streaming
  • Ngày 50: Error Boundaries - RSC dùng Error Boundaries
  • Ngày 1-45: Tất cả React fundamentals - Foundation cho RSC

Hướng tới

  • Ngày 52: Project 7 - Tổng hợp Modern React features
  • Next.js Module: Thực hành RSC trong Next.js 13+ App Router
  • Testing Module: Testing Server Components

💡 SENIOR INSIGHTS

Cân Nhắc Production

1. Migration Strategy:

Phase 1: New features only
- Dùng RSC cho new features
- Keep existing code unchanged

Phase 2: High-traffic pages
- Convert product pages, blog posts
- Measure performance impact

Phase 3: Gradual rollout
- Convert dashboard, user pages
- Monitor errors, rollback if needed

Phase 4: Complete migration
- Convert remaining pages
- Remove old patterns

2. Performance Monitoring:

jsx
// Track RSC performance
export async function ProductPage({ params }) {
  const startTime = Date.now();

  const product = await db.products.get(params.id);

  const fetchTime = Date.now() - startTime;
  await analytics.track('rsc_fetch_time', { fetchTime });

  return <ProductDetails product={product} />;
}

3. SEO Benefits:

Traditional React:
- Google sees empty HTML
- Must execute JavaScript
- Slower indexing

RSC:
- Google sees full HTML
- Instant content access
- Faster indexing
- Better rankings

Câu Hỏi Phỏng Vấn

Junior:

  1. RSC là gì?
  2. Server Component khác Client Component như thế nào?
  3. Khi nào dùng Server Component?

Mid:

  1. Explain RSC architecture và rendering flow
  2. So sánh RSC vs SSR vs CSR
  3. Composition patterns trong RSC

Senior:

  1. Design RSC architecture cho large-scale app
  2. Migration strategy từ traditional React sang RSC
  3. Performance optimization với RSC
  4. Trade-offs và khi nào KHÔNG nên dùng RSC

War Stories

Story: The Bundle Size Miracle

"E-commerce app có bundle size 450KB. Users complain slow loads.

Migrated sang RSC:

  • Product pages: 450KB → 80KB
  • Category pages: 380KB → 60KB
  • Homepage: 420KB → 90KB

Result:

  • Load time: 3.2s → 0.8s
  • Conversion rate: +25%
  • Bounce rate: -40%

Lesson: RSC dramatically reduces bundle size → Better UX → More money."


🎯 PREVIEW NGÀY 52

Ngày mai là Project 7 - Modern React App!

Chúng ta sẽ:

  • Tổng hợp tất cả React 18 features
  • Combine Suspense + Error Boundaries
  • Use Concurrent features
  • Build production-ready modern app
  • Apply best practices đã học

Đây là project cuối cùng trước Testing phase! 🎉


🎉 CHÚC MỪNG! Bạn đã hiểu React Server Components!

Bạn đã học được: ✅ RSC concept và architecture ✅ Server vs Client Components ✅ Benefits và trade-offs ✅ Composition patterns ✅ When to use RSC

⚠️ LƯU Ý: RSC chỉ có trong Next.js 13+ App Router. Chúng ta sẽ thực hành trong Next.js module!

Sẵn sàng cho project cuối Modern React phase! 🚀

Personal tech knowledge base