Lewati ke konten utama
Ms.
Blog/Menguasai React Server Components di Next.js: Paradigma Baru Frontend Modern
5 September 2026•8 menit baca

Menguasai React Server Components di Next.js: Paradigma Baru Frontend Modern

Deep dive ke React Server Components (RSC) di Next.js memahami perbedaan Server vs Client Components, kapan menggunakan masing-masing, streaming, suspense boundaries, dan bagaimana RSC secara fundamental mengubah cara kita membangun aplikasi web modern.

Next.js
React
Server Components
Frontend
TypeScript

Sejak diperkenalkan secara stabil di Next.js 13+ dengan App Router, React Server Components (RSC) telah mengubah secara fundamental cara developer berpikir tentang rendering di React. Ini bukan sekadar fitur baru ini adalah paradigma shift yang menghapus batasan lama antara server dan client.

Dalam artikel ini, kita akan membedah RSC secara mendalam: bagaimana cara kerjanya, kapan memilih Server vs Client Component, serta pola-pola arsitektur yang wajib dikuasai setiap developer Next.js modern.


1. Apa Itu React Server Components?

React Server Components adalah komponen React yang dieksekusi sepenuhnya di server dan mengirimkan hasil rendering (bukan JavaScript) ke browser. Ini berarti:

  • Zero client-side JavaScript untuk server components bundle size lebih kecil
  • Akses langsung ke database, file system, dan API tanpa endpoint terpisah
  • Streaming kirim HTML secara bertahap saat data siap

Perbedaan kunci: Berbeda dengan SSR tradisional yang mengirim HTML lalu hydrate seluruh halaman dengan JavaScript, RSC mengirim serialized component tree dan hanya mengirim JavaScript untuk komponen yang benar-benar interaktif (Client Components).

Alur Eksekusi React Server Components (RSC) Bagaimana Next.js membagi eksekusi antara Server (Zero JS) dan Client (Selective Hydration)
SERVER (Next.js Runtime) Direct Data Fetching Query DB / Filesystem / Private APIs (No CORS/Secrets leak) Server Component Execution Komponen dieksekusi di server 0 KB JavaScript dikirim Serialisasi: HTML + RSC Payload Streaming HTML instan + representasi JSON virtual DOM Bundling: Pisahkan Client Leaf Islands Stream CLIENT (Browser) Fast First Contentful Paint (FCP) Browser langsung merender HTML statis tanpa tunggu JS Minimal JS Bundle Download Hanya mengunduh JS untuk file ber-directive "use client" Selective Hydration Hydration hanya terjadi pada tombol & komponen interaktif Hasil: TTI Cepat & Memory Ringan

2. Server vs Client Components: Kapan Menggunakan Apa?

Di Next.js App Router, semua komponen adalah Server Components secara default. Kamu perlu menambahkan directive "use client" secara eksplisit untuk menjadikannya Client Component.

Gunakan Server Component ketika:

Kebutuhan Contoh
Fetching data Query database, panggil API
Akses resource backend File system, environment variables
Menampilkan konten statis Blog post, halaman marketing
Komponen tanpa interaktivitas Header statis, footer, layout

Gunakan Client Component ketika:

Kebutuhan Contoh
Event handler onClick, onChange, onSubmit
State & lifecycle useState, useEffect, useRef
Browser API window, localStorage, navigator
Third-party library interaktif Chart library, map, rich text editor

3. Contoh Praktis: Data Fetching di Server Component

Salah satu keunggulan utama RSC adalah kemampuan fetch data langsung di komponen tanpa perlu API route terpisah:

// app/dashboard/page.tsx   Server Component (default)
import { db } from '@/lib/database';
import { DashboardStats } from './DashboardStats';
import { RecentOrders } from './RecentOrders';

interface Order {
  id: string;
  customer: string;
  total: number;
  status: 'pending' | 'completed' | 'cancelled';
  createdAt: Date;
}

export default async function DashboardPage() {
  // Langsung query database   tidak perlu API route!
  const [stats, recentOrders] = await Promise.all([
    db.query<{ totalRevenue: number; orderCount: number }>(
      'SELECT SUM(total) as totalRevenue, COUNT(*) as orderCount FROM orders'
    ),
    db.query<Order>(
      'SELECT * FROM orders ORDER BY created_at DESC LIMIT 10'
    ),
  ]);

  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold">Dashboard</h1>

      {/* Server Component   tidak perlu "use client" */}
      <DashboardStats data={stats} />

      {/* Server Component dengan data dari DB */}
      <RecentOrders orders={recentOrders} />
    </main>
  );
}

Bandingkan dengan pendekatan tradisional yang membutuhkan:

  1. Membuat API route (/api/dashboard)
  2. Fetch dari client dengan useEffect
  3. Manage loading/error state dengan useState
  4. Handle race conditions

Dengan RSC, semua itu tidak diperlukan.


4. Komposisi: Menyisipkan Client Component di Server Component

Pola paling umum adalah Server Component sebagai container yang mem-passing data ke Client Component interaktif:

// app/products/page.tsx   Server Component
import { getProducts } from '@/lib/api';
import { ProductFilter } from '@/components/ProductFilter'; // Client
import { ProductCard } from '@/components/ProductCard';       // Server

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

  return (
    <div className="grid gap-6">
      {/* Client Component untuk interaksi filter */}
      <ProductFilter categories={products.categories} />

      {/* Server Components untuk display   zero JS */}
      <div className="grid grid-cols-3 gap-4">
        {products.items.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}
// components/ProductFilter.tsx   Client Component
"use client";

import { useState, useTransition } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';

interface ProductFilterProps {
  categories: string[];
}

export function ProductFilter({ categories }: ProductFilterProps) {
  const router = useRouter();
  const searchParams = useSearchParams();
  const [isPending, startTransition] = useTransition();
  const [selected, setSelected] = useState(
    searchParams.get('category') || 'all'
  );

  const handleFilter = (category: string) => {
    setSelected(category);
    startTransition(() => {
      const params = new URLSearchParams(searchParams.toString());
      if (category === 'all') {
        params.delete('category');
      } else {
        params.set('category', category);
      }
      router.push(`/products?${params.toString()}`);
    });
  };

  return (
    <div className="flex gap-2">
      <button
        onClick={() => handleFilter('all')}
        className={selected === 'all' ? 'font-bold' : ''}
      >
        Semua
      </button>
      {categories.map((cat) => (
        <button
          key={cat}
          onClick={() => handleFilter(cat)}
          className={selected === cat ? 'font-bold' : ''}
          disabled={isPending}
        >
          {isPending && selected === cat ? '...' : cat}
        </button>
      ))}
    </div>
  );
}

Pola penting: Jangan menambahkan "use client" di level page. Sebaliknya, buat leaf components yang spesifik sebagai Client Components dan biarkan page/layout tetap sebagai Server Components.


5. Streaming & Suspense: Loading yang Lebih Cerdas

RSC mendukung streaming mengirim bagian halaman yang sudah siap sementara bagian lain masih di-fetch. Gunakan <Suspense> untuk mengontrol loading boundaries:

// app/analytics/page.tsx
import { Suspense } from 'react';
import { SlowChart } from './SlowChart';
import { FastStats } from './FastStats';

export default function AnalyticsPage() {
  return (
    <main>
      <h1>Analytics</h1>

      {/* Ini muncul duluan karena cepat */}
      <Suspense fallback={<StatsSkeleton />}>
        <FastStats />
      </Suspense>

      {/* Ini di-stream setelah data chart siap */}
      <Suspense fallback={<ChartSkeleton />}>
        <SlowChart />
      </Suspense>
    </main>
  );
}

function StatsSkeleton() {
  return (
    <div className="grid grid-cols-4 gap-4">
      {[...Array(4)].map((_, i) => (
        <div key={i} className="h-24 bg-gray-200 animate-pulse rounded-lg" />
      ))}
    </div>
  );
}

function ChartSkeleton() {
  return (
    <div className="h-96 bg-gray-200 animate-pulse rounded-lg" />
  );
}

Keuntungan streaming:

  • Time to First Byte (TTFB) lebih cepat
  • User melihat konten bermakna lebih awal
  • Tidak perlu loading spinner full-page

6. Kesalahan Umum yang Harus Dihindari

❌ Import server-only module di Client Component

// ❌ SALAH   akan error
"use client";
import { db } from '@/lib/database'; // Server-only module!

❌ Passing non-serializable props

// ❌ SALAH   function tidak bisa di-serialize
<ClientComponent onClick={() => console.log('click')} />

// ✅ BENAR   pass data, bukan function
<ClientComponent items={items} />

❌ Menggunakan "use client" di setiap file

// ❌ SALAH   jangan jadikan semua client component
"use client"; // Tidak perlu jika komponen tidak interaktif!

export function StaticCard({ title }: { title: string }) {
  return <div>{title}</div>;
}

7. Performa: Dampak Nyata RSC

Mengadopsi RSC secara benar memberikan improvement performa yang signifikan:

  • Bundle size berkurang 30-70% library berat (markdown parser, syntax highlighter, date formatter) hanya berjalan di server
  • First Contentful Paint (FCP) lebih cepat HTML dikirim langsung, bukan menunggu JS di-download dan dieksekusi
  • SEO lebih baik konten sudah ada di HTML response, tidak perlu client-side rendering

Kesimpulan

React Server Components bukan sekadar fitur tambahan ini adalah evolusi fundamental dalam cara kita membangun aplikasi React. Dengan memahami kapan menggunakan Server vs Client Component, memanfaatkan streaming dengan Suspense, dan menghindari kesalahan umum, kamu bisa membangun aplikasi Next.js yang lebih cepat, lebih ringan, dan lebih scalable.

Kunci utamanya: biarkan komponen menjadi Server Component secara default, dan hanya tambahkan "use client" ketika benar-benar dibutuhkan untuk interaktivitas.