Di Next.js modern, terutama saat memakai App Router, kita tidak lagi menganggap semua komponen React berjalan dengan cara yang sama. Ada Server Component dan Client Component, dan keputusan memilih salah satunya berpengaruh langsung pada performa, SEO, ukuran bundle JavaScript, keamanan, serta pengalaman pengguna.

Artikel ini membahas dasar yang penting tetapi tetap teknis: apa bedanya Server Component dan Client Component, kapan masing-masing cocok dipakai, trade-off yang perlu dipahami, dan contoh implementasi yang realistis pada halaman daftar produk.

Inti singkat: jika komponen hanya menampilkan data dan tidak butuh interaksi browser, prioritaskan Server Component. Jika komponen membutuhkan state, event handler, atau API browser seperti window dan localStorage, gunakan Client Component.

Apa Itu Server Component dan Client Component?

Server Component

Server Component adalah komponen React yang dirender di server. Secara default, komponen dalam folder app/ di Next.js diperlakukan sebagai Server Component kecuali diberi directive 'use client'.

Karena berjalan di server, komponen ini bisa:

  • Mengakses database atau service backend secara langsung.
  • Membaca environment variable yang bersifat rahasia.
  • Melakukan fetch data tanpa mengirim logika fetch tersebut ke browser.
  • Mengurangi JavaScript yang perlu diunduh client.

Namun, Server Component tidak bisa memakai hal-hal yang hanya tersedia di browser, seperti:

  • useState, useEffect, dan hook interaktif lain.
  • Event handler seperti onClick, onChange, onSubmit.
  • Browser API seperti window, document, navigator, localStorage.

Client Component

Client Component adalah komponen yang berjalan di browser. Untuk menandainya, tambahkan 'use client' di baris paling atas file.

Komponen ini cocok untuk:

  • UI interaktif.
  • State lokal.
  • Event handler.
  • Akses browser API.
  • Integrasi library yang bergantung pada DOM atau lifecycle browser.

Konsekuensinya, Client Component akan ikut membentuk bundle JavaScript yang dikirim ke browser. Semakin banyak Client Component, semakin besar potensi biaya unduh, parse, dan hydration.

Kapan Sebaiknya Pakai Server Component?

Pilih Server Component sebagai default untuk bagian UI yang tidak memerlukan interaksi langsung di browser.

1. Menampilkan data dari server

Misalnya daftar produk, artikel, profil pengguna, statistik dashboard, atau hasil query database. Jika data bisa dirender langsung menjadi HTML tanpa state interaktif di browser, Server Component biasanya pilihan terbaik.

2. Mengejar performa dan ukuran bundle yang lebih kecil

Karena logic komponen tetap di server, browser menerima HTML hasil render dan hanya sedikit atau bahkan tanpa JavaScript tambahan untuk bagian itu. Ini membantu mempercepat initial load, terutama di perangkat mobile atau jaringan lambat.

3. SEO lebih baik untuk konten utama

Konten yang dirender di server lebih mudah tersedia sejak awal pada HTML response. Untuk halaman seperti katalog produk, artikel blog, halaman dokumentasi, dan landing page berbasis konten, ini biasanya lebih ramah untuk crawler dan lebih cepat terlihat oleh pengguna.

4. Menjaga data sensitif tetap di server

Jika komponen perlu memakai secret key, koneksi database, atau logic backend yang tidak boleh bocor ke browser, gunakan Server Component. Ini bukan hanya soal kenyamanan, tetapi juga soal keamanan dan batas trust antara server dan client.

Contoh: daftar produk dirender di server

Berikut contoh sederhana halaman produk yang datanya diambil di server dan dirender sebagai Server Component.

// app/products/page.tsx
import ProductFilter from './ProductFilter'

type Product = {
  id: string
  name: string
  price: number
  category: string
}

async function getProducts(): Promise<Product[]> {
  const res = await fetch('https://api.example.com/products', {
    // sesuaikan strategi cache dengan kebutuhan aplikasi
    next: { revalidate: 300 }
  })

  if (!res.ok) {
    throw new Error('Gagal mengambil data produk')
  }

  return res.json()
}

export default async function ProductsPage() {
  const products = await getProducts()

  return (
    <main>
      <h1>Daftar Produk</h1>
      <ProductFilter products={products} />
    </main>
  )
}

Pada contoh di atas, halaman tetap memanfaatkan server untuk mengambil data. Ini efisien karena data produk tidak perlu difetch ulang dari browser hanya untuk menampilkan daftar awal.

Kapan Sebaiknya Pakai Client Component?

Gunakan Client Component ketika UI butuh interaksi langsung setelah halaman dimuat.

1. Saat butuh state lokal

Contoh: filter, dropdown, tab, modal, input pencarian, form dengan validasi realtime, atau accordion. Semua ini memerlukan useState atau hook sejenis.

2. Saat butuh event handler

Jika komponen harus merespons klik tombol, perubahan input, drag-and-drop, atau event lain dari pengguna, maka komponen itu harus menjadi Client Component.

3. Saat butuh browser API

Misalnya membaca preferensi dari localStorage, mengecek ukuran viewport, memakai IntersectionObserver, atau memanggil API yang hanya tersedia di browser.

4. Saat memakai library UI yang bergantung pada DOM

Banyak library chart, rich text editor, date picker, dan animation library memerlukan browser environment. Komponen pembungkusnya perlu dijadikan Client Component.

Contoh: filter interaktif di client

Berikut contoh filter kategori dan pencarian nama produk di sisi client. Data awal tetap datang dari server, tetapi filtering dilakukan interaktif di browser.

// app/products/ProductFilter.tsx
'use client'

import { useMemo, useState } from 'react'

type Product = {
  id: string
  name: string
  price: number
  category: string
}

export default function ProductFilter({ products }: { products: Product[] }) {
  const [query, setQuery] = useState('')
  const [category, setCategory] = useState('all')

  const filteredProducts = useMemo(() => {
    return products.filter((product) => {
      const matchQuery = product.name.toLowerCase().includes(query.toLowerCase())
      const matchCategory = category === 'all' || product.category === category
      return matchQuery && matchCategory
    })
  }, [products, query, category])

  return (
    <section>
      <div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem' }}>
        <input
          type="text"
          placeholder="Cari produk"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
        />

        <select value={category} onChange={(e) => setCategory(e.target.value)}>
          <option value="all">Semua kategori</option>
          <option value="keyboard">Keyboard</option>
          <option value="mouse">Mouse</option>
          <option value="monitor">Monitor</option>
        </select>
      </div>

      <ul>
        {filteredProducts.map((product) => (
          <li key={product.id}>
            {product.name} - Rp {product.price.toLocaleString('id-ID')}
          </li>
        ))}
      </ul>
    </section>
  )
}

Pola ini umum dipakai: data utama dirender di server, interaksi kecil diletakkan pada client. Hasilnya biasanya lebih seimbang daripada menjadikan seluruh halaman sebagai Client Component.

Trade-off Penting: Performa, SEO, Keamanan, dan Bundle Size

Performa

Server Component umumnya unggul untuk initial render karena lebih sedikit JavaScript yang harus dieksekusi di browser. Namun, jika interaksi pengguna sangat intens dan kompleks, sebagian UI tetap lebih tepat ditangani di client.

Client Component memberi pengalaman interaktif yang responsif setelah hydration selesai, tetapi ada biaya tambahan berupa:

  • Ukuran bundle lebih besar.
  • Waktu parse dan execute JavaScript lebih banyak.
  • Biaya hydration di browser.

Praktik yang baik adalah menjaga boundary client sekecil mungkin. Jangan menambahkan 'use client' pada file layout atau halaman utama hanya karena satu tombol kecil butuh interaksi.

SEO

Untuk konten utama yang harus cepat tampil dan mudah diindeks, Server Component lebih menguntungkan. HTML sudah tersedia dari server sehingga crawler dan pengguna menerima isi halaman lebih awal.

Client Component tetap bisa dipakai pada halaman yang penting untuk SEO, tetapi sebaiknya terbatas pada bagian interaktif, bukan keseluruhan konten utama.

Keamanan

Server Component lebih aman untuk logic yang berhubungan dengan:

  • Token privat.
  • Kredensial database.
  • Validasi backend.
  • Konsumsi API internal yang tidak boleh diekspos ke browser.

Kesalahan umum adalah memindahkan terlalu banyak logic ke client, lalu tanpa sadar mengekspos detail request atau struktur data internal. Rule praktisnya: jika tidak perlu diketahui browser, simpan di server.

Ukuran bundle

Setiap Client Component dan dependensinya berpotensi masuk ke bundle browser. Jika sebuah komponen besar diberi 'use client', seluruh subtree impor yang relevan dapat ikut terbawa ke sisi client. Inilah alasan mengapa boundary komponen penting.

Strategi yang biasanya baik:

  • Biarkan halaman dan komponen daftar utama tetap di server.
  • Pindahkan hanya widget interaktif kecil ke client.
  • Hindari menjadikan komponen utilitas berat sebagai dependency dari Client Component bila tidak perlu.

Tabel Keputusan Sederhana

KebutuhanPilihAlasan
Menampilkan data dari API/databaseServer ComponentEfisien, aman, SEO lebih baik, bundle client lebih kecil
Form interaktif dengan validasi realtimeClient ComponentButuh state, event handler, dan update UI instan
Daftar artikel atau produk untuk halaman publikServer ComponentKonten utama cocok dirender di server
Modal, dropdown, tab, accordionClient ComponentBergantung pada interaksi pengguna
Mengakses localStorage atau windowClient ComponentBrowser API tidak tersedia di server
Menggunakan secret key atau query database langsungServer ComponentHarus tetap berada di sisi server
Filter kecil untuk data yang sudah ada di halamanClient Component kecil + data dari serverKombinasi paling umum dan efisien

Kesalahan Umum yang Sering Terjadi

1. Menaruh state di Server Component

Contoh yang salah:

// SALAH
import { useState } from 'react'

export default function ProductList() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

Ini akan gagal karena Server Component tidak boleh memakai hook interaktif. Solusinya adalah memindahkan bagian interaktif itu ke Client Component dengan menambahkan 'use client'.

2. Menaruh event handler di Server Component

Contoh yang salah:

// SALAH
export default function ProductItem() {
  return <button onClick={() => alert('klik')}>Beli</button>
}

Event handler hanya bisa berjalan di browser. Jika ada onClick, onChange, atau handler lain, komponen tersebut harus menjadi Client Component atau dipisah menjadi child component client.

3. Terlalu cepat memberi 'use client' pada komponen besar

Ini sering terjadi saat developer menemukan error hook atau event handler, lalu solusi instannya adalah menjadikan seluruh halaman sebagai Client Component. Secara fungsional mungkin berhasil, tetapi sering merusak optimasi:

  • Bundle membesar.
  • Initial load lebih berat.
  • Logic data fetching berpindah ke browser tanpa alasan kuat.

Lebih baik pecah komponen: bagian non-interaktif tetap di server, bagian interaktif kecil dipindahkan ke client.

4. Mengakses browser API di server

Contoh seperti ini akan error:

// SALAH
const theme = localStorage.getItem('theme')

localStorage tidak tersedia di server. Jika benar-benar diperlukan, akses di Client Component, biasanya di dalam effect atau saat event tertentu terjadi.

Pola Implementasi yang Direkomendasikan

  1. Mulai dari Server Component sebagai default. Ini sejalan dengan tujuan mengurangi JavaScript di client.
  2. Pisahkan island interaktif. Buat komponen client kecil untuk filter, form, tombol aksi, atau widget UI.
  3. Kirim data yang memang dibutuhkan saja ke client. Jangan teruskan objek besar jika hanya butuh beberapa field.
  4. Jaga secret dan logic sensitif tetap di server. Browser bukan tempat untuk kredensial atau akses langsung ke sistem internal.
  5. Evaluasi dependency. Library berat di Client Component akan memengaruhi bundle.

Tips Debugging dan Cara Berpikir Saat Memilih

Jika bingung, tanyakan beberapa hal berikut:

  • Apakah komponen ini butuh klik, input, atau state lokal?
  • Apakah komponen ini perlu window, document, atau localStorage?
  • Apakah ada data sensitif atau akses backend langsung?
  • Apakah konten ini penting untuk SEO dan initial render?
  • Apakah saya bisa memecah komponen agar hanya bagian kecil yang jadi client?

Jika jawaban dominan ada pada interaktivitas browser, pilih Client Component. Jika fokusnya rendering data dan keamanan backend, pilih Server Component.

Aturan praktis: render sebanyak mungkin di server, interaksikan seperlunya di client.

Penutup

Server Component dan Client Component bukan dua pilihan yang saling menggantikan sepenuhnya, melainkan dua alat yang dipakai bersama. Server Component cocok untuk rendering data, SEO, keamanan, dan efisiensi bundle. Client Component diperlukan untuk state, event handler, dan browser API.

Pada banyak kasus Next.js, pendekatan terbaik adalah kombinasi keduanya: daftar produk dirender di server, lalu filter dan interaksi kecil dikelola di client. Dengan pola ini, aplikasi tetap cepat saat pertama kali dimuat, tetap ramah SEO, dan tetap nyaman digunakan secara interaktif.

Jika Anda baru memulai, gunakan aturan sederhana ini: jadikan Server Component sebagai default, lalu tambahkan Client Component hanya di area yang benar-benar membutuhkan interaksi browser.