# React 19: Server Components trong Production - Hướng dẫn đầy đủ > Làm chủ React 19 Server Components trong môi trường production. Kiến trúc, pattern, streaming, caching và tối ưu hóa cho ứng dụng hiệu suất cao. - Published: 2026-01-08 - Updated: 2026-04-06 - Author: SharpSkill - Tags: react 19, server components, rsc, performance, next.js - Reading time: 14 min --- Server Components là bước tiến hóa quan trọng nhất trong React kể từ khi Hooks ra đời. Với React 19, kiến trúc này đã trưởng thành và sẵn sàng cho môi trường production, cho phép các component thực thi trực tiếp trên server trong khi vẫn duy trì khả năng tương tác phía client. > **Điều kiện tiên quyết** > > Hướng dẫn này giả định bạn đã quen thuộc với React và Next.js App Router. Các ví dụ sử dụng Next.js 14+ - phiên bản hỗ trợ React Server Components một cách mặc định. ## Tìm hiểu kiến trúc Server Components Server Components (RSC) giới thiệu một mô hình mới: một số component chỉ chạy trên server, một số khác chạy trên client, và cả hai có thể cùng tồn tại trong cùng một cây component. Sự tách biệt này tối ưu hóa hiệu suất một cách đáng kể bằng cách giảm thiểu bundle JavaScript gửi đến trình duyệt. Ý tưởng cơ bản dựa trên thực tế rằng nhiều component không cần tính tương tác. Một component hiển thị danh sách bài viết từ cơ sở dữ liệu có thể chạy hoàn toàn phía server. Chỉ những phần tử tương tác (nút bấm, biểu mẫu, hiệu ứng động) mới cần JavaScript phía client. ```tsx // app/articles/page.tsx // This component runs only on the server // No JavaScript is sent to the client for this component import { getArticles } from '@/lib/articles' import ArticleCard from './ArticleCard' import LikeButton from './LikeButton' // async/await directly in the component // Only possible with Server Components export default async function ArticlesPage() { // Direct database call (no REST API needed) const articles = await getArticles() return (

Recent Articles

{articles.map((article) => ( // ArticleCard is also a Server Component {/* LikeButton is a Client Component (interactive) */} ))}
) } ``` Directive `"use client"` đánh dấu rõ ràng các component cần JavaScript phía trình duyệt. ```tsx // app/articles/LikeButton.tsx 'use client' // useState and interactive hooks require "use client" import { useState, useTransition } from 'react' import { likeArticle } from '@/actions/articles' interface LikeButtonProps { articleId: string initialLikes?: number } export default function LikeButton({ articleId, initialLikes = 0 }: LikeButtonProps) { // Local state for optimistic UI const [likes, setLikes] = useState(initialLikes) const [isPending, startTransition] = useTransition() const handleLike = () => { // Immediate optimistic update setLikes((prev) => prev + 1) // Server Action to persist startTransition(async () => { await likeArticle(articleId) }) } return ( ) } ``` Kiến trúc này giảm đáng kể kích thước bundle JavaScript: chỉ mã nguồn của `LikeButton` được gửi đến client, không phải `ArticlesPage` hay `ArticleCard`. ## Các pattern kết hợp Server/Client Việc kết hợp giữa Server và Client Components tuân theo các quy tắc cụ thể. Server Component có thể import và render Client Component, nhưng chiều ngược lại không thể thực hiện trực tiếp. Để truyền nội dung từ server sang client component, pattern `children` là giải pháp. ```tsx // components/InteractiveWrapper.tsx 'use client' import { useState, ReactNode } from 'react' interface InteractiveWrapperProps { children: ReactNode expandable?: boolean } // Client Component that wraps server content export function InteractiveWrapper({ children, expandable = false }: InteractiveWrapperProps) { const [isExpanded, setIsExpanded] = useState(!expandable) if (!expandable) { return
{children}
} return (
{isExpanded && (
{/* children can contain Server Components */} {children}
)}
) } ``` ```tsx // app/dashboard/page.tsx // Server Component using the client wrapper import { InteractiveWrapper } from '@/components/InteractiveWrapper' import { getStats, getRecentActivity } from '@/lib/dashboard' export default async function DashboardPage() { // Parallel server-side requests const [stats, activity] = await Promise.all([ getStats(), getRecentActivity() ]) return (
{/* Stats in an expandable wrapper */} {/* This content is rendered server-side then passed to client */}
{/* Recent activity */}
) } ``` Pattern này cho phép kết hợp tính tương tác phía client với dữ liệu được render phía server mà không cần sao chép logic. ## Truy xuất dữ liệu và Caching > **Fetch được mở rộng bởi React** > > React 19 tự động mở rộng API `fetch` gốc để thêm khả năng loại bỏ trùng lặp và caching. Các request giống nhau trong cùng một lần render chỉ được thực thi một lần duy nhất. Việc truy xuất dữ liệu trong Server Components được thực hiện trực tiếp với `async/await`. React tự động xử lý việc loại bỏ các request trùng lặp. ```tsx // lib/api.ts // Centralized request configuration with caching const API_BASE = process.env.API_URL // Request with time-based revalidation export async function getProducts() { const response = await fetch(`${API_BASE}/products`, { // Revalidate every hour next: { revalidate: 3600 } }) if (!response.ok) { throw new Error('Failed to fetch products') } return response.json() } // Request without cache (real-time data) export async function getCurrentUser() { const response = await fetch(`${API_BASE}/me`, { // No cache, always fresh cache: 'no-store' }) if (!response.ok) { return null } return response.json() } // Request with tag for targeted invalidation export async function getProduct(id: string) { const response = await fetch(`${API_BASE}/products/${id}`, { next: { tags: [`product-${id}`], revalidate: 3600 } }) if (!response.ok) { throw new Error('Product not found') } return response.json() } ``` Đối với truy cập cơ sở dữ liệu trực tiếp (Prisma, Drizzle), React cache với `unstable_cache` cung cấp các khả năng tương tự. ```tsx // lib/db-queries.ts import { unstable_cache } from 'next/cache' import { prisma } from '@/lib/prisma' // Cache categories (rarely modified) export const getCategories = unstable_cache( async () => { return prisma.category.findMany({ orderBy: { name: 'asc' } }) }, ['categories'], // Cache key { revalidate: 86400, // 24 hours tags: ['categories'] } ) // Cache products by category export const getProductsByCategory = unstable_cache( async (categoryId: string) => { return prisma.product.findMany({ where: { categoryId }, include: { images: true }, orderBy: { createdAt: 'desc' } }) }, ['products-by-category'], { revalidate: 3600, tags: ['products'] } ) // Cache invalidation after mutation export async function createProduct(data: ProductInput) { const product = await prisma.product.create({ data }) // Invalidate related caches revalidateTag('products') return product } ``` ## Streaming và Suspense cho trải nghiệm người dùng tối ưu Streaming cho phép gửi HTML một cách tuần tự đến trình duyệt, hiển thị ngay các phần đã sẵn sàng trong khi các phần khác vẫn đang tải. Kết hợp với Suspense, cơ chế này cải thiện đáng kể Time to First Byte (TTFB) và trải nghiệm người dùng cảm nhận được. ```tsx // app/product/[id]/page.tsx import { Suspense } from 'react' import { getProduct } from '@/lib/products' import ProductDetails from './ProductDetails' import ProductReviews from './ProductReviews' import RecommendedProducts from './RecommendedProducts' import { Skeleton } from '@/components/ui/Skeleton' interface ProductPageProps { params: { id: string } } export default async function ProductPage({ params }: ProductPageProps) { // This request blocks initial render const product = await getProduct(params.id) return (
{/* Immediate render with product data */} {/* Reviews load via streaming */}

Customer Reviews

}> {/* This async component will be streamed */}
{/* Recommendations too */}

Similar Products

}>
) } // Skeleton for reviews function ReviewsSkeleton() { return (
{[1, 2, 3].map((i) => (
))}
) } ``` ```tsx // app/product/[id]/ProductReviews.tsx // Async Server Component that will be streamed import { getProductReviews } from '@/lib/reviews' interface ProductReviewsProps { productId: string } export default async function ProductReviews({ productId }: ProductReviewsProps) { // This request may take time // Component will be streamed when complete const reviews = await getProductReviews(productId) if (reviews.length === 0) { return (

No reviews yet. Be the first to share your thoughts!

) } return (
{reviews.map((review) => (
{review.author} {'★'.repeat(review.rating)}{'☆'.repeat(5 - review.rating)}

{review.content}

))}
) } ``` Trình duyệt nhận được khung HTML cùng với skeleton trước, sau đó nội dung thực tế được chèn dần dần thông qua streaming. ## Server Actions cho Mutations Server Actions cho phép thực thi mã server từ client component mà không cần tạo API route. Cách tiếp cận này đơn giản hóa đáng kể việc xử lý mutation. ```tsx // actions/cart.ts 'use server' import { revalidatePath } from 'next/cache' import { cookies } from 'next/headers' import { prisma } from '@/lib/prisma' import { getCurrentUser } from '@/lib/auth' // Action to add to cart export async function addToCart(productId: string, quantity: number = 1) { const user = await getCurrentUser() if (!user) { // Return structured error return { error: 'Login required', code: 'UNAUTHORIZED' } } try { // Check stock const product = await prisma.product.findUnique({ where: { id: productId } }) if (!product || product.stock < quantity) { return { error: 'Insufficient stock', code: 'OUT_OF_STOCK' } } // Add or update cart item await prisma.cartItem.upsert({ where: { cartId_productId: { cartId: user.cartId, productId } }, update: { quantity: { increment: quantity } }, create: { cartId: user.cartId, productId, quantity } }) // Invalidate cart page cache revalidatePath('/cart') return { success: true, message: 'Product added to cart' } } catch (error) { console.error('Add to cart error:', error) return { error: 'An error occurred', code: 'SERVER_ERROR' } } } // Action to remove from cart export async function removeFromCart(itemId: string) { const user = await getCurrentUser() if (!user) { return { error: 'Login required' } } await prisma.cartItem.delete({ where: { id: itemId, cart: { userId: user.id } } }) revalidatePath('/cart') return { success: true } } ``` ```tsx // components/AddToCartButton.tsx 'use client' import { useTransition } from 'react' import { addToCart } from '@/actions/cart' import { toast } from '@/components/ui/toast' interface AddToCartButtonProps { productId: string } export function AddToCartButton({ productId }: AddToCartButtonProps) { const [isPending, startTransition] = useTransition() const handleClick = () => { startTransition(async () => { const result = await addToCart(productId) if (result.error) { toast.error(result.error) return } toast.success(result.message) }) } return ( ) } ``` > **Xác thực phía server** > > Luôn xác thực dữ liệu trong Server Actions. Các xác thực phía client có thể bị bỏ qua. Hãy sử dụng Zod hoặc thư viện tương tự để đảm bảo xác thực chắc chắn. ## Xử lý lỗi và Error Boundaries React 19 cải thiện việc xử lý lỗi với Server Components. Error Boundaries hoạt động tương tự như với Client Components. ```tsx // app/products/error.tsx 'use client' // Error Boundary for /products segment interface ErrorProps { error: Error & { digest?: string } reset: () => void } export default function ProductsError({ error, reset }: ErrorProps) { return (

Loading Error

Unable to load products. Please try again.

{/* Display digest for debugging */} {error.digest && (

Reference: {error.digest}

)}
) } ``` ```tsx // app/products/loading.tsx // Loading UI during initial load export default function ProductsLoading() { return (
{[1, 2, 3, 4, 5, 6].map((i) => (
))}
) } ``` Để xử lý lỗi chi tiết hơn ở từng component cụ thể, có thể sử dụng trực tiếp component `ErrorBoundary`. ```tsx // components/ErrorBoundary.tsx 'use client' import { Component, ReactNode } from 'react' interface Props { children: ReactNode fallback?: ReactNode } interface State { hasError: boolean error?: Error } export class ErrorBoundary extends Component { constructor(props: Props) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error: Error): State { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // Log error to monitoring service console.error('ErrorBoundary caught:', error, errorInfo) } render() { if (this.state.hasError) { return this.props.fallback || (

An error occurred

) } return this.props.children } } ``` ## Tối ưu hóa hiệu suất cho Production Một số kỹ thuật giúp tối ưu hóa hiệu suất Server Components trong môi trường production. ```tsx // app/layout.tsx import { Suspense } from 'react' import { headers } from 'next/headers' // Preload critical data export const dynamic = 'force-dynamic' export default async function RootLayout({ children }: { children: React.ReactNode }) { return ( {/* Header streamed independently */} }>
{children}
{/* Footer can be static */}