Server vs Client Component
Memahami dua jenis komponen: di mana render dan apa bedanya.
Dua dapur dalam satu restoran
Bayangkan restoran dengan dua dapur. Dapur utama (server) punya akses ke gudang bahan, resep rahasia, dan kompor industri: semua masakan berat dikerjakan di sana, lalu hidangan jadi diantar ke meja. Dapur kecil di meja tamu (client) hanya untuk hal yang harus dilakukan di depan tamu: memotong buah segar atau menyalakan lilin.
Server Component dan Client Component bekerja dengan pembagian seperti itu. Server Component "memasak" di server: mengambil data dari database, membaca API key rahasia, me-render HTML jadi, lalu mengirim hasilnya ke browser. Client Component "diselesaikan" di browser agar bisa interaktif: merespons klik, menyimpan state, membaca localStorage.
Kenapa pembagian ini ada
Di era React lama, SEMUA komponen dikirim sebagai JavaScript ke browser, lalu browser me-render semuanya. Akibatnya: bundle besar, halaman lambat di HP kentang, dan data sensitif gampang bocor karena kodenya terlihat di DevTools.
Next.js App Router membalik defaultnya: semua komponen adalah Server Component kecuali kamu menandainya "use client". Hasilnya: JavaScript yang dikirim ke browser jauh lebih sedikit, data rahasia tetap di server, dan halaman terasa cepat.
Contoh 1: halaman server + pulau interaktif
// app/page.tsx (Server Component, default, tanpa "use client")
import { db } from "@/lib/db";
import LikeButton from "./LikeButton";
export default async function HomePage() {
const posts = await db.post.findMany({ take: 10 }); // aman: query di server
return (
<main>
<h1>Artikel terbaru</h1>
<ul>
{posts.map((p) => (
<li key={p.id}>
{p.title} <LikeButton postId={p.id} />
</li>
))}
</ul>
</main>
);
}// app/LikeButton.tsx
"use client";
import { useState } from "react";
export default function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)} aria-pressed={liked}>
{liked ? "❤️" : "🤍"} Suka
</button>
);
}Daftar artikel di-render di server (cepat, SEO-friendly, tanpa JS), hanya tombol like yang dikirim JavaScript-nya ke browser. Pola "pulau interaktif di lautan konten statis" ini adalah pola paling umum di App Router. Perhatikan props yang dikirim ke Client Component (postId, string) sederhana dan serializable.
Contoh 2: butuh API browser? wajib client
// app/WidthInfo.tsx
"use client";
import { useEffect, useState } from "react";
export default function WidthInfo() {
const [width, setWidth] = useState(0);
useEffect(() => {
const update = () => setWidth(window.innerWidth); // window hanya ada di browser
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
return <p>Lebar layar saat ini: {width}px</p>;
}window, document, localStorage, dan navigator tidak ada di server. Kode yang menyentuhnya harus tinggal di Client Component. Kalau kamu memaksa memakainya di Server Component, dapat error window is not defined saat render.
Tabel keputusan cepat
| Kebutuhan | Pilih |
|---|---|
| Fetch data / query database | Server |
| Membaca API key atau secret | Server |
| Menampilkan daftar konten statis | Server |
useState, useEffect, event handler | Client |
Browser API (window, localStorage) | Client |
| Library pihak ketiga yang interaktif | Client (dibungkus tipis) |
Satu kesalahpahaman yang paling sering ditemui: "use client" BUKAN berarti "hanya di-render di client". Komponen client tetap di-prerender di server menjadi HTML awal, lalu "dihidupkan" di browser lewat proses bernama hydrasi. Arti tepat "use client" adalah: komponen ini boleh memakai fitur client, dan JavaScript-nya akan dikirim ke browser. Jadi jangan panik kalau komponen client tetap muncul di HTML awal, itu memang disengaja demi kecepatan tampil pertama.
Tantangan
Klasifikasi
Klasifikasikan 5 kebutuhan ini ke Server/Client: (a) baca database, (b) tombol like, (c) baca localStorage, (d) tampilkan daftar produk, (e) validasi form realtime.