📅 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)
- Suspense boundaries hoạt động như thế nào với async data?
- Error Boundaries catch errors ở đâu trong component tree?
- 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
// ❌ 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 tripsReal-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 JavaScript1.2 Giải Pháp: React Server Components
RSC = Components render trên server, gửi kết quả đến client
// ✅ 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 serverFlow 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 second1.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:
// 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)"
// 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 clientComparison:
| Feature | SSR | RSC |
|---|---|---|
| Where code runs | Server + Client | Server only |
| JavaScript sent | Full component code | Zero (for server components) |
| Can use useState | ✅ Yes | ❌ No |
| Can use useEffect | ✅ Yes | ❌ No |
| Can access database | ❌ No (only in getServerSideProps) | ✅ Yes (directly) |
| Bundle size impact | Large (all code) | Small (only client components) |
❌ Hiểu lầm 2: "Mọi component phải là Server Component"
// ❌ 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"
// ❌ 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 ⭐
/**
* 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 needsDemo 2: Data Fetching Patterns ⭐⭐
/**
* 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 UXDemo 3: Composition Patterns ⭐⭐⭐
/**
* 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)
/**
* 🎯 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
/**
* 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)
/**
* 🎯 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
/**
* 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)
/**
* 🎯 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
/**
* 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
| Aspect | Traditional React (CSR) | SSR (Next.js Pages) | RSC (Next.js App Router) |
|---|---|---|---|
| Rendering Location | Client only | Server + Client | Server (default) + Client (opt-in) |
| JavaScript Bundle | Large (all code) | Large (all code) | Small (only Client Components) |
| Data Fetching | Client (useEffect) | getServerSideProps | Direct in component (async) |
| SEO | Poor (needs JS) | Good (HTML ready) | Excellent (HTML + streaming) |
| Time to Interactive | Slow (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 states | Required | Required | Optional (use Suspense) |
| Complexity | Medium | High | Medium-High (new concepts) |
| Performance | Poor | Good | Excellent |
| When to use | Simple SPAs | SEO-critical pages | Modern 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 partsComposition 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
/**
* 🐛 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
/**
* 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 propsBug 2: Importing Server Component in Client Component
/**
* 🐛 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
/**
* 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
/**
* 🐛 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
/**
* 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:
function Component() {
const data = await fetch('/api/data');
return <div>{data.title}</div>;
}❓ Server hay Client? → Server (async, fetch data)
Scenario 2:
function Component() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}❓ Server hay Client? → Client (useState, onClick)
Scenario 3:
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:
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
Đọc thêm
🔗 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 patterns2. Performance Monitoring:
// 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 rankingsCâu Hỏi Phỏng Vấn
Junior:
- RSC là gì?
- Server Component khác Client Component như thế nào?
- Khi nào dùng Server Component?
Mid:
- Explain RSC architecture và rendering flow
- So sánh RSC vs SSR vs CSR
- Composition patterns trong RSC
Senior:
- Design RSC architecture cho large-scale app
- Migration strategy từ traditional React sang RSC
- Performance optimization với RSC
- 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! 🚀