# React 19: Server Components ในระบบ Production - คู่มือฉบับสมบูรณ์ > เชี่ยวชาญ React 19 Server Components ในระบบ Production สถาปัตยกรรม รูปแบบการออกแบบ Streaming Caching และการเพิ่มประสิทธิภาพสำหรับแอปพลิเคชันที่มีประสิทธิภาพสูง - 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 ถือเป็นการพัฒนาที่สำคัญที่สุดใน React นับตั้งแต่การเปิดตัว Hooks ด้วย React 19 สถาปัตยกรรมนี้ได้เติบโตจนพร้อมสำหรับการใช้งานในระบบ Production ทำให้ Component สามารถทำงานได้โดยตรงบนเซิร์ฟเวอร์ในขณะที่ยังคงรักษาความสามารถในการโต้ตอบฝั่ง Client ไว้ได้ > **ข้อกำหนดเบื้องต้น** > > คู่มือนี้ถูกเขียนขึ้นโดยสมมติว่าท่านมีความคุ้นเคยกับ React และ Next.js App Router ตัวอย่างที่นำเสนอใช้ Next.js 14+ ซึ่งรองรับ React Server Components โดยค่าเริ่มต้น ## ทำความเข้าใจสถาปัตยกรรม Server Components Server Components (RSC) นำเสนอแนวคิดใหม่ โดย Component บางส่วนทำงานเฉพาะบนเซิร์ฟเวอร์ บางส่วนทำงานบน Client และทั้งสองสามารถอยู่ร่วมกันในโครงสร้าง Component เดียวกันได้ การแบ่งแยกนี้ช่วยเพิ่มประสิทธิภาพอย่างมากด้วยการลดขนาด JavaScript Bundle ที่ส่งไปยังเบราว์เซอร์ แนวคิดพื้นฐานตั้งอยู่บนความจริงที่ว่า Component จำนวนมากไม่จำเป็นต้องมีการโต้ตอบ Component ที่แสดงรายการบทความจากฐานข้อมูลสามารถทำงานได้ทั้งหมดบนเซิร์ฟเวอร์ มีเพียงองค์ประกอบที่ต้องโต้ตอบ (ปุ่ม แบบฟอร์ม แอนิเมชัน) เท่านั้นที่ต้องใช้ JavaScript ฝั่ง 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"` ใช้สำหรับระบุอย่างชัดเจนว่า Component ใดต้องการ JavaScript บนเบราว์เซอร์ ```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 ( ) } ``` สถาปัตยกรรมนี้ช่วยลดขนาด JavaScript Bundle ได้อย่างมาก โดยมีเพียงโค้ดของ `LikeButton` เท่านั้นที่ถูกส่งไปยัง Client ไม่ใช่ `ArticlesPage` หรือ `ArticleCard` ## รูปแบบการประกอบ Server/Client การประกอบระหว่าง Server และ Client Components เป็นไปตามกฎที่ชัดเจน Server Component สามารถ Import และ Render Client Component ได้ แต่ในทางกลับกันไม่สามารถทำได้โดยตรง ในการส่งเนื้อหาจากเซิร์ฟเวอร์ไปยัง Client Component รูปแบบ `children` คือทางออก ```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 */}
) } ``` รูปแบบนี้ช่วยให้สามารถผสมผสานการโต้ตอบฝั่ง Client เข้ากับข้อมูลที่ Render บนเซิร์ฟเวอร์ได้โดยไม่ต้องทำซ้ำ Logic ## การดึงข้อมูลและ Caching > **Fetch ที่ถูกขยายโดย React** > > React 19 ขยาย API `fetch` ดั้งเดิมโดยอัตโนมัติเพื่อเพิ่มการขจัดความซ้ำซ้อนและ Caching Request ที่เหมือนกันภายใน Render เดียวกันจะถูกเรียกใช้เพียงครั้งเดียว การดึงข้อมูลใน Server Components ทำได้โดยตรงด้วย `async/await` React จัดการการขจัด Request ที่ซ้ำซ้อนโดยอัตโนมัติ ```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() } ``` สำหรับการเข้าถึงฐานข้อมูลโดยตรง (Prisma, Drizzle) React Cache ร่วมกับ `unstable_cache` มอบความสามารถเดียวกัน ```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 และ Suspense เพื่อประสบการณ์ผู้ใช้ที่ดีที่สุด Streaming ช่วยให้สามารถส่ง HTML ไปยังเบราว์เซอร์แบบค่อยเป็นค่อยไป แสดงส่วนที่พร้อมแล้วทันทีในขณะที่ส่วนอื่นยังคงโหลดอยู่ เมื่อใช้ร่วมกับ Suspense กลไกนี้ช่วยปรับปรุง Time to First Byte (TTFB) และประสบการณ์ผู้ใช้ได้อย่างเห็นได้ชัด ```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}

))}
) } ``` เบราว์เซอร์จะได้รับโครงสร้าง HTML พร้อม Skeleton ก่อน จากนั้นเนื้อหาจริงจะถูกแทรกเข้ามาทีละส่วนผ่าน Streaming ## Server Actions สำหรับ Mutations Server Actions ช่วยให้สามารถเรียกใช้โค้ดฝั่งเซิร์ฟเวอร์จาก Client Component ได้โดยไม่ต้องสร้าง API Route วิธีการนี้ช่วยลดความซับซ้อนในการจัดการ 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 ( ) } ``` > **การตรวจสอบข้อมูลฝั่งเซิร์ฟเวอร์** > > ควรตรวจสอบความถูกต้องของข้อมูลใน Server Actions เสมอ การตรวจสอบฝั่ง Client สามารถถูกข้ามได้ ใช้ Zod หรือไลบรารีที่คล้ายกันเพื่อให้การตรวจสอบมีความน่าเชื่อถือ ## การจัดการข้อผิดพลาดและ Error Boundaries React 19 ปรับปรุงการจัดการข้อผิดพลาดใน Server Components โดย Error Boundaries ทำงานในลักษณะเดียวกับ 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) => (
))}
) } ``` สำหรับการจัดการข้อผิดพลาดที่ละเอียดยิ่งขึ้นใน Component เฉพาะ สามารถใช้ 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 } } ``` ## การเพิ่มประสิทธิภาพสำหรับ Production เทคนิคหลายประการช่วยเพิ่มประสิทธิภาพของ Server Components ในระบบ 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 */}