Next RSC: Arsitektur React Server Components di Next.js

Next RSC mengacu pada implementasi React Server Components (RSC) di dalam framework Next.js (App Router). Istilah ini juga kerap muncul di Network Tab browser sebagai parameter URL ?_rsc=... atau request header RSC: 1, yaitu format payload data berbasis stream khusus yang dikirimkan server Next.js ke browser saat terjadi client-side navigation tanpa memicu full page reload.

Secara default, seluruh komponen di dalam direktori app/ pada Next.js adalah Server Components. Komponen ini dieksekusi secara eksklusif di server, tidak menyumbang ukuran bundle JavaScript ke browser, serta memiliki akses langsung ke database, environment variables rahasia, maupun filesystem backend.

Perbedaan Fundamental Server Component vs Client Component

Memahami batasan antara Server Component dan Client Component merupakan kunci utama arsitektur Next.js modern. Server Component bertujuan mengurangi bundle size dan mendekatkan komputasi ke sumber data, sementara Client Component fokus pada interaktivitas pengguna di peramban.

Fitur / KarakteristikServer Component (Default)Client Component ('use client')
Lokasi EksekusiHanya di Server (Node.js / Edge Runtime)Pre-render di Server, Hydrate di Browser
Dampak ke JS Bundle0 KB (Zero-bundle impact)Sesuai bobot kode & library impor
Hooks ReactTidak bisa (Tanpa useState, useEffect, dll.)Bisa penuh (useState, useEffect, custom hooks)
Event ListenersTidak mendukung (onClick, onChange, dll.)Mendukung seluruh DOM event listener
Akses Backend LangsungBisa (Prisma, PostgreSQL, fs, process.env)Hanya lewat API route atau Server Actions
Browser API (window/document)Tidak tersedia (akan melempar error)Tersedia setelah tahap mount (useEffect)

Arti Payload Request `_rsc` di Network Tab

Ketika Anda mengklik komponen <Link href="/dashboard">, browser tidak mengunduh file HTML utuh seperti routing tradisional. Next.js melakukan fetch HTTP internal dengan parameter atau header khusus:

GET /dashboard?_rsc=1bq9a HTTP/1.1
Host: example.com
RSC: 1
Next-Router-State-Tree: ...

Respons dari request ini bukan HTML murni atau JSON biasa, melainkan React Server Component Payload. Format payload ini berupa format serialisasi biner/teks yang menguraikan representasi Virtual DOM (UI tree), slot placeholder untuk Client Components, serta props serializable yang siap dipasangkan ke state aplikasi di browser tanpa menghilangkan state DOM global (misal scroll position atau input focus).

Implementasi Kode: Server Component Mengonsumsi Client Component

Pola arsitektur standar Next.js adalah menempatkan Server Component di level terluar (fetching data) dan melemparkan data tersebut ke Client Component yang menangani interaksi pengguna.

1. Komponen Server (app/products/page.tsx)

// Komponen ini otomatis berupa Server Component
import ProductFilter from './ProductFilter';

interface Product {
  id: string;
  name: string;
  price: number;
}

// Mengambil data langsung tanpa useEffect / axios di client
async function getProducts(): Promise<Product[]> {
  const res = await fetch('https://api.example.com/products', {
    next: { revalidate: 60 } // Incremental Static Regeneration (ISR)
  });
  if (!res.ok) throw new Error('Gagal mengambil data produk');
  return res.json();
}

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

  return (
    <main style={{ padding: '24px' }}>
      <h1>Katalog Produk</h1>
      {/* Client Component dimasukkan di sini */}
      <ProductFilter initialData={products} />
    </main>
  );
}

2. Komponen Client (app/products/ProductFilter.tsx)

'use client'; // Menandai boundary sebagai Client Component

import { useState } from 'react';

interface Product {
  id: string;
  name: string;
  price: number;
}

export default function ProductFilter({ initialData }: { initialData: Product[] }) {
  const [query, setQuery] = useState('');

  const filtered = initialData.filter((item) =>
    item.name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="Cari produk..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        style={{ padding: '8px', marginBottom: '16px', display: 'block' }}
      />
      <ul>
        {filtered.map((product) => (
          <li key={product.id}>
            {product.name} - Rp{product.price.toLocaleString('id-ID')}
          </li>
        ))}
      </ul>
    </div>
  );
}

Aturan Batasan Serialisasi RSC Props

Saat Server Component mengirimkan props ke Client Component melalui RSC bridge, data tersebut wajib dapat diserialisasi (serializable) oleh React. Pelanggaran aturan ini menghasilkan error runtime saat proses kompilasi.

  • Tipe Data yang Sah: String, Number, Boolean, BigInt, Array, Plain Object, Date, URL, Buffer, null, undefined, serta React Server Actions (fungsi yang dideklarasikan dengan direktif 'use server').
  • Tipe Data yang Terlarang: Fungsi reguler (closure JavaScript), Class Instances, Symbol, dan DOM elements mentah.

Solusi Masalah Umum (Troubleshooting) Next RSC

1. Error: "useState can only be used in Client Component"

Terjadi karena Anda lupa membubuhkan direktif 'use client'; di baris paling atas file. Tambahkan direktif tersebut sebelum deklarasi import.

2. Error: "Functions cannot be passed directly to Client Components"

Terjadi ketika function standar didefinisikan di Server Component dan dilempar sebagai props ke Client Component. Jika fungsi tersebut bertujuan mengeksekusi operasi server (seperti mutasi database), tambahkan 'use server' di dalam fungsi tersebut untuk menjadikannya Server Action.

3. Bundle Size Membengkak Tak Terduga

Jika sebuah library berat (misal moment.js atau markdown-parser) diimpor di dalam file berlabel 'use client', library tersebut akan dikirim penuh ke peramban. Pindahkan proses komputasi library tersebut ke Server Component agar hasil akhirnya saja (berupa string HTML/teks) yang dikirim ke browser.