nextjsclientlibraryMenengah3 mnt baca

Client Component untuk Library Pihak Ketiga

Membungkus library interaktif (carousel, chart, editor) dengan boundary kecil.

Kenapa library butuh "rumah" client sendiri

Library interaktif seperti chart, carousel, rich text editor, atau peta hampir selalu memakai hal-hal yang hanya ada di browser: window, document, canvas, useEffect, atau CSS-in-JS runtime. Kalau kamu mengimpornya langsung di Server Component, ada dua kemungkinan buruk: crash saat server-side render (document is not defined), atau library raksasa itu ikut terbundel ke tempat yang tidak seharusnya.

Solusinya bukan menghindari library, melainkan memberinya "rumah" client yang kecil dan rapi: sebuah Client Component tipis yang hanya bertugas me-render library itu. Fetch data dan logika bisnis tetap tinggal di Server Component.

Contoh 1: wrapper chart yang tipis

tsx
// app/blog/_components/Chart.tsx
"use client";

import { BarChart, Bar, XAxis, YAxis, Tooltip } from "recharts";

export default function Chart({ data }: { data: { name: string; value: number }[] }) {
  return (
    <BarChart width={500} height={300} data={data}>
      <XAxis dataKey="name" />
      <YAxis />
      <Tooltip />
      <Bar dataKey="value" />
    </BarChart>
  );
}
tsx
// app/blog/page.tsx (server: fetch data, oper ke wrapper)
import Chart from "./_components/Chart";
import { getVisitStats } from "@/lib/stats";

export default async function BlogPage() {
  const raw = await getVisitStats(); // di server: cepat dan aman
  const data = raw.map((r) => ({ name: r.month, value: r.visits })); // plain object

  return (
    <main>
      <h1>Statistik kunjungan</h1>
      <Chart data={data} /> {/* data serializable menyeberang ke client */}
    </main>
  );
}

Perhatikan pembagian kerjanya: page.tsx tidak tahu menahu soal recharts, Chart.tsx tidak tahu menahu soal database. Props data berupa array of plain object, jadi aman melewati boundary. Bundle client hanya berisi kode chart, bukan logika fetch.

Contoh 2: library yang crash saat diimpor di server

Sebagian library mengakses window di level module (baris paling atas file), sehingga sekadar mengimpornya saja sudah crash saat SSR. Untuk kasus ini, pakai dynamic import dengan ssr: false:

tsx
// app/peta/_components/MapView.tsx
"use client";

import dynamic from "next/dynamic";

const LeafletMap = dynamic(() => import("./LeafletMapInner"), {
  ssr: false, // jangan render di server sama sekali
  loading: () => <p>Memuat peta...</p>,
});

export default function MapView({ points }: { points: { lat: number; lng: number }[] }) {
  return <LeafletMap points={points} />;
}
tsx
// app/peta/_components/LeafletMapInner.tsx
"use client";

import { MapContainer, TileLayer, Marker } from "react-leaflet";
import "leaflet/dist/leaflet.css";

export default function LeafletMapInner({
  points,
}: {
  points: { lat: number; lng: number }[];
}) {
  return (
    <MapContainer center={[points[0].lat, points[0].lng]} zoom={13} style={{ height: 400 }}>
      <TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
      {points.map((p, i) => (
        <Marker key={i} position={[p.lat, p.lng]} />
      ))}
    </MapContainer>
  );
}

Dengan ssr: false, komponen hanya di-render di browser setelah JavaScript dimuat. Fallback loading menjaga layout tidak meloncat. Pakai pola ini hanya kalau benar-benar perlu: dynamic import menunda tampilnya komponen sampai JS selesai diunduh.

Kebiasaan baik untuk tim

  • Bungkus setipis mungkin. Wrapper idealnya hanya meneruskan props ke library. Logika fetch, transformasi data, dan state bisnis tetap di luar wrapper.
  • Beri nama yang jelas. Di tim besar, nama file eksplisit seperti Chart.client.tsx memudahkan audit "mana saja yang menjadi client" tanpa membuka tiap file.
  • Satu wrapper, satu library. Jangan mencampur dua library dalam satu Client Component, nanti boundary-nya membesar tanpa disadari dan susah dipecah lagi.

Tantangan

Wrapper chart

Install satu library chart ringan, bungkus dalam Client Component, dan tampilkan data yang di-fetch dari Server Component. Pastikan fetch tidak terjadi di browser (cek Network).