Sejak 2013-2023, paradigma React = client-side rendering (CSR): HTML kosong, browser download bundle JS, JS yang render UI + fetch data. Hasilnya: bundle gede + waktu load lambat + SEO ribet. Tahun 2023, React Team rilis React Server Components (RSC) — komponen yang BENERAN dirender di server, kirim HTML siap pake ke browser, JavaScript-nya gak masuk bundle. Hasilnya: bundle kecil + first paint cepat + akses DB langsung di komponen + SEO clean. Pattern ini ngubah cara React dibangun. Next.js App Router (2023+) adalah implementasi RSC paling populer.
⚠️ Bukan SSR biasa. Beda dari Next.js Pages Router yang SSR (render HTML, lalu hydrate dengan FULL bundle JS). RSC: server render HTML, JavaScript komponen TIDAK masuk bundle browser. Cuma client component yang interactive yang ke-bundle. Saving bundle size signifikan untuk app dengan banyak component "non-interactive" (header, footer, list dari DB).
Perbedaan Server vs Client Component
| Server Component | Client Component | |
|---|---|---|
| Render | Di server (Node.js) | Di browser |
| Bundle JS | TIDAK masuk bundle | Masuk bundle |
| Hooks | ❌ TIDAK bisa useState/useEffect | ✓ Bisa |
| Event handler | ❌ TIDAK bisa onClick | ✓ Bisa |
| Akses DB | ✓ Langsung (await db.query()) |
❌ Via API |
| Akses env vars | ✓ (termasuk secrets) | ❌ Cuma NEXT_PUBLIC_* |
| Async function | ✓ async function Component() |
❌ |
| Browser API | ❌ (no window, document) | ✓ |
| Default di Next.js App Router | ✓ | Pakai "use client" |
Server Component (Default di Next.js App Router)
// app/users/page.tsx — SERVER component (no "use client")
async function UsersPage() {
const users = await db.query("SELECT * FROM users") // direct DB access!
// Atau fetch API: await fetch("https://api.../users", { cache: "force-cache" })
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
)
}
export default UsersPage
Plus: hasil HTML langsung dari server, gak ada loading spinner, SEO-friendly, no bundle untuk komponen ini.
Client Component (Opt-in)
"use client" // ← marker WAJIB di top
import { useState } from "react"
export default function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
Compose Server + Client
Pattern paling umum: server component fetch data → render layout → embed client component yang interactive.
// app/dashboard/page.tsx — SERVER
import InteractiveChart from "./InteractiveChart" // client component
async function Dashboard() {
const data = await db.query("SELECT * FROM analytics")
return (
<div>
<h1>Dashboard</h1>
<p>Data dari server, langsung tanpa loading.</p>
<InteractiveChart data={data} /> {/* pass server data ke client component */}
</div>
)
}
// InteractiveChart.tsx — CLIENT
"use client"
export default function InteractiveChart({ data }) {
const [filter, setFilter] = useState("all")
// event handler, animation, etc.
}
Server component handle fetching, client component handle interaktivitas. Best of both worlds.
Streaming + Suspense
async function SlowData() {
const data = await fetch("/slow-api", { cache: "no-store" }) // 3 detik
return <Chart data={await data.json()} />
}
function Page() {
return (
<div>
<Header /> {/* render dulu */}
<Suspense fallback={<Spinner />}>
<SlowData /> {/* stream pas data ready */}
</Suspense>
<Footer />
</div>
)
}
Page kirim HTML bertahap — header + footer dulu, slow component nyusul dengan suspense fallback. UX SUPER smooth.
Server Actions (RSC + Mutation)
async function addTodo(formData: FormData) {
"use server" // ← di server
const text = formData.get("text")
await db.insert("todos", { text })
revalidatePath("/todos")
}
function TodoForm() {
return (
<form action={addTodo}> {/* langsung pass server function ke action */}
<input name="text" />
<button>Tambah</button>
</form>
)
}
Form mutation tanpa fetch boilerplate. Direct call from client to server function — Next.js handle serialization.
🎭 Analogi sehari-hari
CSR (cara lama) itu kayak beli IKEA furniture flatpack — kamu dapat kotak gede berisi banyak komponen + buku petunjuk (HTML kosong + bundle JS). Sampai rumah, kamu rakit sendiri (browser execute JS, render UI). Lama, ribet, kalo komponen banyak makin lambat. RSC kayak beli furniture jadi dari toko — udah dirakit di pabrik (server render HTML), tinggal pasang yang variabel (interactive parts = client component). Sampe rumah langsung pakai. Cepat, hemat usaha, hemat ruang gudang JS bundle. Pattern hybrid: lemari basic udah jadi dari pabrik (server), pintu kamu pasang sendiri sesuai selera (client interactive).
⚠️ Jebakan yang sering ditemui
- Pakai
useState/useEffectdi Server Component — ERROR. Tambah"use client"di top atau pisah ke client component child. - Lupa
"use client"— pas import dari client component, kalau component pake hooks, harus marked"use client". - Pass non-serializable props ke client — function (kecuali Server Action), Date, Map, Set — error. Convert dulu atau pakai children pattern.
- Akses
window/documentdi server component — undefined. Wrap di client component atau checktypeof window. - Pikir RSC = SSR — beda. RSC server-render + JS gak bundle. SSR tradisional render dulu di server lalu client hydrate dengan full bundle.
- Bingung Pages Router vs App Router — App Router = RSC default, Pages Router = client by default. Cek mana yang dipake.
- Caching surprise — Next.js cache fetch response by default. Kalo butuh fresh:
{ cache: "no-store" }ataurevalidatePath.
🎯 Pakai Server atau Client component?
- Default → Server Component (di App Router)
- Butuh useState/useEffect/useContext/event handler → Client Component (
"use client")- Butuh browser API (window, localStorage, navigator) → Client
- Akses DB langsung / fetch dengan secrets → Server (jangan expose API key ke browser)
- Render heavy library yang gak butuh interactivity (markdown renderer, syntax highlighter) → Server (saving bundle)
- Form dengan submit ke server → Server Action + form
action={...}- Real-time / WebSocket → Client
Aturan: tarik
"use client"SEDEKAT MUNGKIN ke component yang butuh — jangan di top of tree (lose RSC benefit).
TL;DR: React Server Components (RSC) = komponen render di server, JS-nya GAK masuk bundle browser. Default Next.js App Router. Bisa: async function, akses DB langsung, env vars secret. TIDAK bisa: useState/useEffect/event handler/browser API. Pakai "use client" untuk component interactive. Pattern modern: server component fetch data → client component handle interaktivitas. Server Actions = form mutation tanpa fetch boilerplate. Bundle awal jadi kecil drastis = first paint cepat + SEO clean.