Server Components — React

Sejak 2013-2023, paradigma React = client-side rendering (CSR): HTML kosong, browser download bundle JS, JS yang render UI + fetch data. Hasilnya: bundle gede +

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 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.

Yang akan kamu pelajari