# React Server Components di produksi: pola dan jebakan > React Server Components di produksi: pola yang teruji, anti-pola umum, dan strategi debugging untuk aplikasi Next.js 15 yang andal. - Published: 2026-03-29 - Updated: 2026-05-04 - Author: Anthony Fillion-Maillet - Tags: react server components, next.js 15, rsc patterns, production, react 19 - Reading time: 9 min --- React Server Components (RSC) secara mendasar mengubah cara kerja rendering di sisi server pada Next.js 15, namun penerapan di produksi mengungkap jebakan yang tidak selalu dibahas oleh dokumentasi resmi. Artikel ini menguraikan pola yang berhasil, pola yang rusak, dan cara mendiagnosis masalah sebelum mencapai produksi. > **Server Components dan Client Components** > > Server Component berjalan secara eksklusif di server dan mengirim nol JavaScript ke browser. Client Component (ditandai dengan `"use client"`) berjalan di kedua sisi. Aturannya: jaga Client Components sekecil dan serendah mungkin di pohon komponen. ## Batas server-client: memahami pola boundary Jebakan RSC paling umum melibatkan batas antara Server dan Client Components. Begitu sebuah komponen membawa direktif `"use client"`, **semua anak yang diimpornya juga menjadi Client Components**, meski tanpa direktif tersebut. ```tsx // ProductPage.tsx (Server Component) import { ProductDetails } from './ProductDetails' import { AddToCartButton } from './AddToCartButton' export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params const product = await getProduct(id) return (
{/* Server Component: akses langsung ke DB */} {/* Client Component: interaktivitas terisolasi */}
) } ``` ```tsx // AddToCartButton.tsx (Client Component) 'use client' import { useState } from 'react' export function AddToCartButton({ productId, price }: { productId: string; price: number }) { const [adding, setAdding] = useState(false) async function handleAdd() { setAdding(true) await fetch('/api/cart', { method: 'POST', body: JSON.stringify({ productId, quantity: 1 }), }) setAdding(false) } return ( ) } ``` Pola kunci: meneruskan data sebagai **props yang dapat diserialisasi** dari Server Component ke Client Component. Fungsi, kelas, dan objek Date tidak bisa melintasi batas ini. ## Anti-pola: pembungkus Client Component yang tidak perlu Kesalahan yang sering terjadi adalah membuat Client Component yang membungkus anak-anak Server Component, sehingga memaksa seluruh subtree menjadi sisi klien. ```tsx // PageWrapper.tsx — ANTI-POLA 'use client' import { useState } from 'react' // Seluruh konten anak menjadi sisi klien export function PageWrapper({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState('light') return (
{children}
) } ``` > **children sebagai slot** > > Solusinya: meneruskan Server Components sebagai `children` (pola slot). Anak-anak yang diteruskan sebagai props tetap menjadi Server Components meskipun induknya adalah Client Component. Kode di atas berfungsi dengan benar selama `children` berasal dari induk Server Component. ```tsx // layout.tsx (Server Component) import { PageWrapper } from './PageWrapper' import { HeavyServerContent } from './HeavyServerContent' export default function Layout() { return ( {/* Tetap Server Component meski ada pembungkus klien */} ) } ``` Pola komposisi ini mempertahankan manfaat rendering server untuk konten berat sekaligus memungkinkan interaktivitas pada level pembungkus. ## Penanganan data asinkron: pola fetch dalam komponen React 19 dan Next.js 15 mendukung `async/await` langsung di Server Components. Pola ini menyederhanakan pengambilan data dibandingkan pendekatan `getServerSideProps` yang lama. ```tsx // UserProfile.tsx (Server Component) import { cache } from 'react' // Mengeliminasi panggilan identik dalam render yang sama const getUser = cache(async (userId: string) => { const res = await fetch(`https://api.example.com/users/${userId}`, { next: { revalidate: 3600 }, // Cache selama 1 jam }) if (!res.ok) throw new Error('User not found') return res.json() }) export default async function UserProfile({ userId }: { userId: string }) { const user = await getUser(userId) return (

{user.name}

{user.email}

Anggota sejak {new Date(user.createdAt).toLocaleDateString('id-ID')}

) } ``` Tiga poin penting: - Fungsi `cache()` React mengeliminasi panggilan identik selama satu render server - `next: { revalidate }` mengontrol durasi cache di sisi Next.js - Error pada Server Component asinkron memicu `error.tsx` terdekat ## Jebakan serialisasi: yang tidak bisa melintasi batas Data yang dipertukarkan antara Server dan Client Components harus dapat diserialisasi ke JSON. Berikut adalah hal-hal yang menyebabkan error senyap atau crash. ```tsx // JEBAKAN: meneruskan tipe yang tidak dapat diserialisasi // Fungsi — tidak bekerja { /* server action */ }} /> // Gunakan Server Action yang diimpor sebagai gantinya import { submitForm } from '@/lib/actions/form' // Objek Date — tidak bekerja // String ISO — bekerja // Map, Set, RegExp — tidak bekerja // Objek biasa atau array — bekerja ``` Server Actions (fungsi yang ditandai `"use server"`) adalah pengecualian: mereka bisa diteruskan sebagai props ke Client Component karena Next.js mengubahnya menjadi endpoint HTTP. ## Streaming dan Suspense: pola pemuatan progresif Streaming SSR dengan Suspense mengirim HTML ke browser secara progresif. Pola optimal menggunakan boundary Suspense yang granular di sekitar setiap bagian asinkron. ```tsx // DashboardPage.tsx (Server Component) import { Suspense } from 'react' import { RevenueChart } from './RevenueChart' import { RecentOrders } from './RecentOrders' import { UserStats } from './UserStats' export default function DashboardPage() { return (
}> }> }>
) } ``` Setiap bagian dimuat secara independen. Jika `RevenueChart` memakan 3 detik dan `UserStats` 200 ms, statistik muncul seketika tanpa menunggu grafik. > **Suspense dan SEO** > > Konten di dalam boundary Suspense dirender di server dan disertakan dalam HTML awal. Crawler melihat seluruh konten. Streaming hanya memengaruhi kecepatan pengiriman ke browser, bukan visibilitas SEO. ## Debugging produksi: melacak masalah RSC Error RSC sering kali samar. Tiga teknik diagnostik bekerja di produksi. **1. Mengidentifikasi ketidakcocokan hidrasi** ```tsx // debug-hydration.tsx 'use client' import { useEffect, useState } from 'react' export function HydrationDebug() { const [isClient, setIsClient] = useState(false) useEffect(() => { setIsClient(true) }, []) if (process.env.NODE_ENV !== 'development') return null return (
{isClient ? 'Client' : 'Server'}
) } ``` **2. Mencatat payload RSC** Di Next.js 15, aktifkan logging RSC di `next.config.ts`: ```typescript // next.config.ts const nextConfig = { logging: { fetches: { fullUrl: true, // Menampilkan URL fetch lengkap }, }, } export default nextConfig ``` **3. Memeriksa ukuran payload** Payload RSC yang berlebihan (> 128 KB) menurunkan kinerja. Pantau permintaan jaringan dengan content type `text/x-component` di DevTools. ## Pola lanjutan: komposisi dengan Server Actions Server Actions yang dikombinasikan dengan Server Components menciptakan pola CQRS alami: pembacaan di server (RSC), penulisan melalui actions. ```tsx // TodoList.tsx (Server Component) import { getTodos } from '@/lib/services/todo' import { TodoForm } from './TodoForm' import { deleteTodo } from '@/lib/actions/todo' export default async function TodoList() { const todos = await getTodos() return (
    {todos.map(todo => (
  • {todo.title}
  • ))}
) } ``` ```tsx // actions/todo.ts 'use server' import { revalidatePath } from 'next/cache' import { TodoService } from '@/lib/services/todo' export async function deleteTodo(formData: FormData) { const id = formData.get('id') as string await TodoService.delete(id) revalidatePath('/todos') } ``` Panggilan `revalidatePath` memicu render Server Component yang baru dengan data terbaru, tanpa memuat ulang seluruh halaman. Untuk persiapan wawancara yang lebih mendalam tentang topik-topik ini, silakan lihat modul [Next.js Server Actions](/technologies/react-next/interview-questions/nextjs-server-actions) dan modul [Next.js Data Fetching](/technologies/react-next/interview-questions/nextjs-data-fetching) di SharpSkill. [Dokumentasi resmi React](https://react.dev/reference/rsc/server-components) memuat seluruh spesifikasi Server Components. ## Kesimpulan - Menjaga Client Components tetap kecil dan terisolasi di bagian bawah pohon komponen - Menggunakan pola slot (`children`) untuk mempertahankan Server Components di dalam pembungkus klien - Selalu memverifikasi serialisabilitas props yang melintasi batas server-client - Menempatkan boundary Suspense yang granular di sekitar setiap bagian asinkron yang independen - Memantau ukuran payload RSC di produksi (target < 128 KB) - Menggabungkan Server Components (pembacaan) dan Server Actions (penulisan) untuk pola CQRS alami - Menggunakan `cache()` React untuk mengeliminasi permintaan dalam satu render server --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/react-next/react-server-components-patterns-pitfalls