Kalau kamu aktif di ekosistem React dalam dua tahun terakhir, pasti udah nggak asing lagi sama istilah React Server Components (RSC). Dulu waktu pertama kali diperkenalkan, banyak yang skeptis — termasuk gue sendiri. “Emangnya sebesar itu dampaknya?” Ternyata, oh ternyata, dampaknya luar biasa. Dan di tahun 2026, RSC bukan lagi fitur eksperimental. Ini udah jadi standar baru cara kita membangun aplikasi React.

Artikel ini gue tulis buat kamu yang mau benar-benar paham RSC dari nol sampai bisa dipakai di production. Nggak cuma teori, tapi juga contoh kode nyata, pola arsitektur, dan best practices yang gue pelajari selama migrasi beberapa project ke RSC. Anggap aja ini kayak kita lagi ngopi bareng, terus gue cerita pengalaman gue.

Yuk, mulai.


Apa Itu React Server Components dan Kenapa Kamu Harus Peduli?

Secara sederhana, React Server Components adalah komponen React yang dieksekusi di server, bukan di browser. Hasil render-nya berupa HTML yang dikirim ke client. Bedanya sama Server-Side Rendering (SSR) tradisional? RSC mengirimkan component tree yang sudah di-render, bukan sekadar string HTML. Client-side React bisa langsung “menghidupkan” komponen tersebut tanpa perlu mengirim JavaScript bundle untuk komponen itu.

Bayangin gini: kamu punya halaman blog yang menampilkan daftar artikel dari database. Kalau pakai cara lama (Client Components), JavaScript untuk mengambil data, memproses, dan merender daftar artikel itu semua dikirim ke browser pengguna. Dengan RSC, proses itu terjadi di server. Yang dikirim ke browser cuma hasil akhirnya — HTML yang sudah jadi, plus JavaScript secukupnya untuk interaktivitas.

Hasilnya?

  • Bundle size lebih kecil — library besar seperti marked, prisma, atau ORM database nggak pernah sampai ke client.
  • Data fetching lebih efisien — kamu bisa langsung query database dari komponen, tanpa API layer tambahan.
  • Security lebih baik — logic sensitif (seperti token, secret key, query ke database) tetap di server, nggak pernah exposed ke client.
  • Initial load lebih cepat — pengguna melihat konten lebih cepat karena server sudah mengirim hasil render.

Kalau kamu pernah pakai Next.js 13+ dengan App Router, kemungkinan besar kamu sudah pakai RSC tanpa sadar. Setiap komponen di folder app/ secara default adalah Server Component.


Cara Kerja React Server Components: Dari Server ke Client

Oke, ini bagian yang paling bikin bingung waktu pertama kali gue pelajari. Jadi gue coba jelasin sesederhana mungkin.

Dua Dunia yang Terpisah

RSC memecah komponen React kamu menjadi dua “dunia”:

  1. Server Components — berjalan di server saat request masuk. Nggak bisa pakai useState, useEffect, atau event handler apapun. Tapi bisa langsung akses database, file system, atau environment variables.

  2. Client Components — berjalan di browser. Punya akses penuh ke React hooks, event handlers, dan browser APIs. Ditandai dengan directive 'use client' di paling atas file.

Yang bikin RSC powerful adalah cara kedua dunia ini berinteraksi. Server Component bisa me-render Client Component sebagai children. Client Component bisa menerima data dari Server Component melalui props (asalkan data tersebut serializable — nggak ada function yang dikirim dari server ke client).

Alur Eksekusinya

Begini alurnya secara garis besar:

  1. Client mengirim request ke server.
  2. Server merender komponen-komponen yang bertanda Server Component.
  3. Server mengirim hasil render dalam format RSC Payload (bukan HTML biasa, tapi representasi serializable dari component tree).
  4. Client-side React menerima payload ini, membangun component tree, dan me-render HTML di browser.
  5. Client Component yang ada di tree tersebut dihidupkan (hydrated) dengan JavaScript yang dikirim terpisah.

Nah, yang menarik: proses streaming. Server nggak harus menunggu semua komponen selesai di-render baru mengirim data. Komponen yang sudah selesai bisa langsung dikirim, sambil komponen lain masih diproses. Ini yang bikin pengguna melihat konten lebih cepat — bahkan sebelum semuanya selesai.


Implementasi Praktis: Contoh Kode RSC di Next.js

Biar nggak cuma teori, mari kita lihat contoh nyata. Di 2026, Next.js dengan App Router tetap jadi framework utama yang mendukung RSC secara penuh.

Server Component Dasar

Buat file app/articles/page.tsx:

// TANPA 'use client' — ini Server Component secara default
import { db } from '@/lib/db'
import { ArticleCard } from '@/components/ArticleCard'

export default async function ArticlesPage() {
  // Langsung query database! Nggak perlu API route.
  const articles = await db.article.findMany({
    orderBy: { publishedAt: 'desc' },
    take: 10,
  })

  return (
    <main className="max-w-4xl mx-auto py-8">
      <h1 className="text-3xl font-bold mb-6">Artikel Terbaru</h1>
      <div className="grid gap-6">
        {articles.map((article) => (
          <ArticleCard key={article.id} article={article} />
        ))}
      </div>
    </main>
  )
}

Perhatikan: kita langsung await db.article.findMany(...) di dalam komponen. Nggak ada useEffect, nggak ada loading state, nggak ada API route. Database query terjadi di server, dan hasilnya langsung dirender.

Client Component untuk Interaktivitas

Sekarang, bagaimana kalau kita butuh interaksi di dalam card — misalnya tombol “like”?

// components/LikeButton.tsx
'use client'

import { useState } from 'react'

interface LikeButtonProps {
  initialCount: number
  articleId: string
}

export function LikeButton({ initialCount, articleId }: LikeButtonProps) {
  const [count, setCount] = useState(initialCount)
  const [liked, setLiked] = useState(false)

  async function handleLike() {
    setLiked(!liked)
    setCount(liked ? count - 1 : count + 1)

    await fetch(`/api/articles/${articleId}/like`, {
      method: 'POST',
    })
  }

  return (
    <button
      onClick={handleLike}
      className={`px-4 py-2 rounded ${liked ? 'bg-red-500 text-white' : 'bg-gray-200'}`}
    >
      {liked ? '❤️' : '🤍'} {count}
    </button>
  )
}

Dan ArticleCard bisa memanggilnya:

// components/ArticleCard.tsx
import { LikeButton } from './LikeButton'
import type { Article } from '@prisma/client'

// Komponen ini TIDAK punya 'use client', jadi Server Component
export function ArticleCard({ article }: { article: Article }) {
  return (
    <article className="border rounded-lg p-6">
      <h2 className="text-xl font-semibold">{article.title}</h2>
      <p className="text-gray-600 mt-2">{article.summary}</p>
      <div className="mt-4">
        {/* Client Component di-render sebagai child */}
        <LikeButton
          initialCount={article.likeCount}
          articleId={article.id}
        />
      </div>
    </article>
  )
}

Pola ini yang gue sebut “islands of interactivity” — sebagian besar halaman adalah server-rendered (cepat, ringan), tapi bagian yang butuh interaksi diisi oleh client components yang kecil-kecil.

Server Actions: Mengubah Data Tanpa API Route

Di 2026, Server Actions udah jadi cara utama untuk melakukan mutasi data. Ini semacam function yang berjalan di server, tapi bisa dipanggil langsung dari client component:

// app/actions/article.ts
'use server'

import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'

export async function createArticle(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string

  await db.article.create({
    data: { title, content },
  })

  revalidatePath('/articles')
}
// app/articles/new/page.tsx
import { createArticle } from '@/app/actions/article'

export default function NewArticlePage() {
  return (
    <form action={createArticle} className="max-w-2xl mx-auto py-8">
      <input
        name="title"
        placeholder="Judul artikel"
        className="w-full border p-3 rounded mb-4"
      />
      <textarea
        name="content"
        placeholder="Isi artikel..."
        className="w-full border p-3 rounded mb-4 h-40"
      />
      <button
        type="submit"
        className="bg-blue-600 text-white px-6 py-3 rounded"
      >
        Publish
      </button>
    </form>
  )
}

Form ini bisa berfungsi bahkan tanpa JavaScript di client. Server Action dipanggil via form action native browser. Tapi begitu JavaScript ter-load, form ini otomatis jadi progressive enhancement — bisa dipanggil tanpa full page reload.


Pola Arsitektur dan Best Practices di Tahun 2026

Setelah migrasi beberapa project ke RSC, gue menemukan beberapa pola yang works dan beberapa yang perlu dihindari.

Pola 1: Server Component Sebagai “Shell”, Client Component Sebagai “Interaksi”

Ini prinsip paling fundamental. Secara default, buat semua komponen sebagai Server Component. Hanya tambahkan 'use client' kalau komponen itu benar-benar butuh:

  • State (useState, useReducer)
  • Effect (useEffect, useLayoutEffect)
  • Browser API (window, document, navigator)
  • Event handler (onClick, onChange)
  • Custom hooks yang menggunakan hal-hal di atas

Kalau kamu menemukan dirimu menambahkan 'use client' di lebih dari 30% komponen, kemungkinan besar ada yang salah dengan arsitekturmu.

Pola 2: Push Client Components Sedalam Mungkin

Jangan buru-buru marking parent component sebagai Client Component. Coba dorong 'use client' ke komponen yang paling leaf — yang paling spesifik butuh interaksi.

❌ Buruk:

'use client'

export function ProductPage({ product }) {
  const [quantity, setQuantity] = useState(1)
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <div>
        <button onClick={() => setQuantity(q => q - 1)}>-</button>
        <span>{quantity}</span>
        <button onClick={() => setQuantity(q => q + 1)}>+</button>
      </div>
    </div>
  )
}

✅ Baik:

// ProductPage.tsx — Server Component
import { QuantitySelector } from './QuantitySelector'

export async function ProductPage({ productId }: { productId: string }) {
  const product = await getProduct(productId)
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <QuantitySelector productId={product.id} />
    </div>
  )
}
// QuantitySelector.tsx — Client Component
'use client'

export function QuantitySelector({ productId }: { productId: string }) {
  const [quantity, setQuantity] = useState(1)
  return (
    <div>
      <button onClick={() => setQuantity(q => Math.max(1, q - 1))}>-</button>
      <span>{quantity}</span>
      <button onClick={() => setQuantity(q => q + 1)}>+</button>
    </div>
  )
}

Dengan cara ini, ProductPage tetap Server Component. Yang dikirim ke client cuma QuantitySelector yang kecil.

Pola 3: Suspense Boundaries untuk Streaming

Manfaatkan <Suspense> untuk menampilkan fallback sementara komponen yang berat masih di-render:

import { Suspense } from 'react'
import { ArticleList } from '@/components/ArticleList'
import { Sidebar } from '@/components/Sidebar'
import { TrendingPosts } from '@/components/TrendingPosts'

export default function HomePage() {
  return (
    <div className="grid grid-cols-3 gap-8">
      <main className="col-span-2">
        <Suspense fallback={<ArticleListSkeleton />}>
          <ArticleList />
        </Suspense>
      </main>
      <aside>
        <Suspense fallback={<SidebarSkeleton />}>
          <Sidebar />
          <TrendingPosts />
        </Suspense>
      </aside>
    </div>
  )
}

ArticleList dan TrendingPosts mungkin butuh query database yang berbeda, dengan waktu eksekusi yang berbeda. Dengan Suspense, yang selesai duluan langsung dikirim ke client. Pengguna nggak perlu menunggu semua selesai.

Pola 4: Hindari “Client Boundary” yang Terlalu Luas

Satu kesalahan yang sering gue lihat: developer menaruh 'use client' di layout.tsx level tertinggi. Ini bikin semua komponen di bawahnya jadi client component. Padahal mungkin cuma satu tombol di navbar yang butuh interaksi.

Solusinya: pisahkan. Buat Navbar.tsx sebagai Server Component, dan MobileMenuButton.tsx sebagai Client Component yang kecil.


Kesalahan Umum yang Harus Dihindari

Selama mentoring beberapa developer yang baru belajar RSC, ini kesalahan yang paling sering gue temui:

1. Mengirim Function sebagai Props dari Server ke Client

// ❌ NGGAK BISA
import { ClientButton } from './ClientButton'

export default function ServerPage() {
  function handleClick() {
    console.log('clicked')
  }
  return <ClientButton onClick={handleClick} />
}

Server Component mengirim data ke Client Component dalam bentuk serializable. Function nggak bisa di-serialize. Solusinya: gunakan Server Actions, atau pindahkan handler ke Client Component.

2. Menggunakan Hooks di Server Component

// ❌ Error!
export default function ServerPage() {
  const [count, setCount] = useState(0) // useState nggak ada di server
  return <div>{count}</div>
}

Kalau kamu butuh state, tandai komponen itu sebagai Client Component. Atau, pertimbangkan apakah kamu benar-benar butuh state — mungkin cukup server-side data yang berbeda berdasarkan URL atau search params.

3. Import Client Component yang Nggak Perlu

Setiap kali kamu import komponen yang punya 'use client', otomatis subtree-nya juga jadi client bundle. Ini bisa bikin bundle size membengkak tanpa kamu sadari.

4. Nggak Manfaatkan Caching

RSC bisa di-cache di berbagai level — edge, CDN, bahkan React sendiri punya mekanisme caching untuk request yang sama. Di 2026, framework seperti Next.js udah punya caching defaults yang sangat baik. Tapi kamu tetap perlu memahami revalidatePath, revalidateTag, dan cara mengontrol cache behavior.


RSC di Luar Next.js: Apa yang Berubah di 2026?

Satu pertanyaan yang sering muncul: “RSC itu cuma untuk Next.js?”

Jawabannya: tidak. RSC adalah fitur React, bukan fitur Next.js. Tapi memang Next.js yang pertama dan paling matang mengimplementasikannya.

Di 2026, beberapa framework dan tools lain yang sudah mendukung RSC:

  • Remix — sekarang sudah fully mendukung RSC dengan React Router v7.
  • Waku — framework ringan yang memang dibangun di atas RSC dari awal.
  • Vite — plugin RSC untuk Vite sudah stabil dan bisa dipakai di custom setup.
  • Expo — untuk React Native, RSC mulai masuk ke ranah mobile (masih experimental tapi promising).

Jadi kalau kamu bukan pengguna Next.js, jangan khawatir. RSC tetap bisa kamu manfaatkan di stack favoritmu.


Kapan Sebaiknya Nggak Pakai RSC?

Ya, RSC bukan silver bullet. Ada beberapa situasi di mana pendekatan tradisional (full client-side) mungkin lebih masuk akal:

  • Aplikasi yang sangat interaktif — misalnya editor dokumen kolaboratif, atau tool desain visual. Komponen-komponennya hampir semuanya butuh state dan event handler. RSC nggak banyak membantu di sini.
  • Dashboard dengan real-time updates — kalau setiap komponen butuh WebSocket atau polling, sebagian besar logic tetap di client.
  • Prototype atau MVP cepat — kalau kamu butuh shipping dalam seminggu dan nggak masalah bundle size, mungkin overhead belajar RSC nggak worth it untuk fase itu.

Tapi untuk sebagian besar aplikasi web — blog, e-commerce, landing page, SaaS dashboard, CMS — RSC hampir selalu memberikan manfaat yang signifikan.


Checklist Migrasi ke RSC

Buat yang mau mulai migrasi project existing, ini checklist yang gue pakai:

  1. Upgrade ke Next.js App Router (atau framework lain yang mendukung RSC).
  2. Audit komponen — identifikasi mana yang butuh interaktif, mana yang bisa jadi Server Component.
  3. Pindahkan data fetching — dari useEffect + API call ke direct async/await di Server Component.
  4. Hapus API routes yang nggak perlu — kalau hanya dipanggil dari satu halaman, cukup pakai Server Action.
  5. Optimasi bundle — cek dengan @next/bundle-analyzer dan pastikan library berat nggak sampai ke client.
  6. Implementasi caching — atur revalidate di level page dan data fetching.
  7. Test streaming — pastikan Suspense boundaries bekerja dan user experience tetap baik.
  8. Monitor performa — pakai Core Web Vitals untuk memastikan ada peningkatan.

Penutup

React Server Components bukan sekadar trend yang lewat. Ini adalah evolusi fundamental dalam cara kita membangun aplikasi React. Di tahun 2026, hampir semua library besar udah dioptimasi untuk RSC. Ekosistem tooling — dari IDE support, debugging tools, sampai deployment platform — udah matang.

Kalau kamu belum mulai explore RSC, sekarang waktu yang tepat. Mulai dari project kecil, rasakan perbedaannya, dan secara bertahap migrasikan project yang lebih besar.

Dan kalau kamu butuh bantuan untuk migrasi, diskusi arsitektur, atau sekadar tanya-tanya soal RSC — feel free reach out ke gue.


Punya pertanyaan atau butuh bantuan soal implementasi React Server Components di project kamu?

📧 Hubungi gue di [email protected] — gue senang bisa diskusi dan bantu solve problem yang kamu hadapi.


FAQ (Frequently Asked Questions)

Apakah React Server Components menggantikan SSR?

Nggak tepat. RSC dan SSR adalah hal yang berbeda, tapi bisa bekerja bersama. SSR merender komponen ke HTML di server saat request masuk, tapi komponen tersebut tetap di-hydrate di client (JavaScript-nya tetap dikirim). RSC di sisi lain, komponennya memang hanya berjalan di server — JavaScript-nya nggak pernah dikirim ke client. Di Next.js App Router, keduanya dipakai bersamaan: RSC di-render di server, lalu hasilnya di-stream ke client sebagai bagian dari SSR process.

Apakah saya harus pakai Next.js untuk menggunakan RSC?

Tidak wajib. RSC adalah fitur React core. Tapi di tahun 2026, Next.js App Router tetap jadi cara paling matang dan stabil untuk menggunakannya. Alternatif lain termasuk Remix/React Router v7, Waku, dan Vite dengan RSC plugin. Kalau kamu sudah comfortable dengan Next.js, itu starting point yang paling baik.

Bagaimana cara handle authentication di React Server Components?

Di Server Component, kamu bisa langsung check session/token tanpa membuat API route. Misalnya dengan NextAuth.js atau framework auth lainnya: cukup panggil auth() atau getSession() langsung di server component. Data user bisa dipakai untuk conditional rendering atau dikirim sebagai props ke Client Component. Yang penting: jangan pernah mengirim secret/token ke Client Component — cukup data user yang dibutuhkan untuk render.

RSC bikin aplikasi lebih cepat, tapi seberapa besar perbedaannya?

Tergantung aplikasinya. Untuk aplikasi yang banyak menampilkan konten dinamis dari database, perbedaannya bisa sangat signifikan — gue pernah melihat penurunan Time to First Byte (TTFB) sampai 60% dan pengurangan JavaScript bundle sampai 40-70%. Untuk aplikasi yang sangat interaktif, perbedaannya mungkin kurang terasa karena sebagian besar komponen tetap butuh jadi Client Component. Tapi secara umum, hampir semua aplikasi web mendapat manfaat dari penurunan bundle size dan peningkatan initial load performance.

Bagaimana cara testing React Server Components?

Server Component bisa di-test sebagai async function biasa — kamu bisa mock database layer dan verify output HTML-nya. Client Component tetap di-test seperti biasa (React Testing Library, Vitest, dll). Untuk integration testing, tools seperti Playwright dan Cypress tetap bekerja karena mereka test dari perspektif user — nggak peduli komponennya server atau client.