React Server Components: Cara Berpikir yang Benar untuk Developer 2026
RSC bukan cuma fitur baru — ini perubahan paradigma cara kita tulis React. Setelah 2 tahun sejak adopsi luas, ini panduan komprehensif cara berpikir dan implementasi RSC yang benar.
Muhamad Putra Aulia Hidayat
React Server Components: Mental Model yang Benar
Setelah 2 tahun React Server Components (RSC) jadi mainstream lewat Next.js App Router, masih banyak developer yang salah kaprah. Artikel ini untuk meluruskan.
Pertanyaan Dasar: Server atau Client?
Aturan sederhana:
Default: Server Component
Gunakan Client Component HANYA kalau:
- Butuh useState atau useReducer
- Butuh useEffect
- Butuh browser APIs (window, document, localStorage)
- Butuh event handlers (onClick, onChange, dll)
- Butuh third-party library yang tidak support server
Visualisasi Component Tree
app/
└── Layout (Server) ← Fetch user dari DB
├── Navbar (Server) ← Render navigasi
│ └── UserMenu (Client) ← Butuh state untuk dropdown
├── Sidebar (Server) ← Data statis
└── Main (Server)
├── ProductList (Server) ← Fetch dari DB langsung
│ └── AddToCart (Client) ← Butuh state cart
└── Pagination (Client) ← Butuh state halaman
Server Components bisa render Client Components, tapi Client Components tidak bisa render Server Components (secara langsung).
Fetch Data di Server Component
// app/products/page.tsx - Server Component
import { createClient } from "@/lib/supabase/server"
export default async function ProductsPage() {
// Fetch langsung di component, tanpa useEffect atau SWR
const supabase = await createClient()
const { data: products } = await supabase
.from("products")
.select("*")
.order("created_at", { ascending: false })
return (
<div>
<h1>Products</h1>
<ProductGrid products={products ?? []} />
</div>
)
}
// ProductGrid juga Server Component
function ProductGrid({ products }: { products: Product[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{products.map(p => (
<ProductCard key={p.id} product={p} />
// ProductCard bisa Server Component kalau tidak butuh interaktivitas
))}
</div>
)
}
Pass Server Data ke Client Component
// Server Component: fetch data, pass ke Client
export default async function CartPage() {
const supabase = await createClient()
const { data: items } = await supabase.from("cart_items").select("*, product:products(*)")
// Pass data sebagai props ke Client Component
return <CartClient initialItems={items ?? []} />
}
// Client Component: handle interactivity
"use client"
function CartClient({ initialItems }: { initialItems: CartItem[] }) {
const [items, setItems] = useState(initialItems)
const removeItem = (id: string) => {
setItems(prev => prev.filter(i => i.id !== id))
// Optimistic update + server action
}
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.product.name}
<button onClick={() => removeItem(item.id)}>Hapus</button>
</div>
))}
</div>
)
}
Server Actions: Forms Tanpa API Routes
// app/actions.ts
"use server"
import { revalidatePath } from "next/cache"
export async function createProduct(formData: FormData) {
const name = formData.get("name") as string
const price = Number(formData.get("price"))
// Validasi
if (!name || price <= 0) {
return { error: "Data tidak valid" }
}
const supabase = await createServerClient()
const { error } = await supabase.from("products").insert({ name, price })
if (error) return { error: "Gagal menyimpan produk" }
// Revalidate halaman produk
revalidatePath("/products")
return { success: true }
}
// Pakai di form - tidak perlu API route!
function ProductForm() {
return (
<form action={createProduct}>
<input name="name" placeholder="Nama produk" />
<input name="price" type="number" placeholder="Harga" />
<button type="submit">Simpan</button>
</form>
)
}
Kesalahan Umum yang Harus Dihindari
// SALAH: Fetch di Client Component
"use client"
function Products() {
const [products, setProducts] = useState([])
useEffect(() => {
fetch("/api/products").then(r => r.json()).then(setProducts)
}, [])
// Loading state, error handling yang manual...
}
// BENAR: Fetch di Server Component
async function Products() {
const products = await getProducts() // Langsung, clean
return <div>{products.map(...)}</div>
}
// SALAH: Client Component yang bisa jadi Server
"use client"
function ProductCard({ product }: { product: Product }) {
// Tidak ada state/event, tidak perlu "use client"!
return <div>{product.name}</div>
}
Kuncinya: selalu tanya "apakah komponen ini butuh interaktivitas atau browser API?" Kalau tidak, biarkan jadi Server Component.
Newsletter Digital Uptime
Tips teknologi & bisnis mingguan
Bergabung dengan 2,500+ subscriber yang mendapatkan insight teknologi, tutorial development, dan tips bisnis digital langsung ke inbox mereka setiap minggu.