# 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!
)
}
```
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 */}
)
}
```
```tsx
// lib/prefetch.ts
// Data prefetching for links
import { preload } from 'react-dom'
export function prefetchProduct(productId: string) {
// Prefetch images
preload(`/api/products/${productId}/image`, { as: 'image' })
}
// Usage in a component
// prefetchProduct(id)}>
```
```tsx
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Build optimizations
experimental: {
// Enable Partial Prerendering (PPR)
ppr: true,
// Optimize package imports
optimizePackageImports: ['lucide-react', '@radix-ui/react-icons']
},
// Image configuration
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{ hostname: 'cdn.example.com' }
]
}
}
module.exports = nextConfig
```
## Kết luận
React 19 Server Components thay đổi căn bản cách xây dựng ứng dụng React. Những điểm chính cần ghi nhớ:
- Tách biệt server/client: chỉ sử dụng `"use client"` cho các component tương tác
- Truy xuất dữ liệu trực tiếp: `async/await` trong component, không cần useEffect hay API route
- Streaming với Suspense: hiển thị tuần tự giúp cải thiện trải nghiệm người dùng
- Server Actions: đơn giản hóa mutation mà không cần tạo API endpoint
- Caching thông minh: `revalidate` và `tags` để tối ưu hiệu suất
- Kết hợp linh hoạt: pattern `children` để phối hợp Server và Client Components
Kiến trúc này cho phép tạo ra các ứng dụng có hiệu suất cao hơn với ít JavaScript phía client hơn, đồng thời đơn giản hóa mã nguồn đáng kể. Chuyển đổi sang Server Components là một khoản đầu tư mang lại giá trị nhanh chóng về mặt hiệu suất và khả năng bảo trì.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/vi/blog/react-next/react-19-server-components-production