Partikel Acak Bebas Masalah Hydration
Pembangkitan animasi partikel melayang dengan nilai acak di sisi klien tanpa memicu bentrokan render server-klien (SSR Hydration Mismatch).
Membaca sekitar 2 menit
Pembangkitan animasi partikel melayang dengan nilai acak di sisi klien tanpa memicu bentrokan render server-klien (SSR Hydration Mismatch).
Menghasilkan 12 partikel hati di seksi Hero dengan ukuran, posisi horizontal, kecepatan, dan goyangan (drift) acak menggunakan useEffect setelah komponen terpasang di peramban.
Memanggil Math.random() langsung saat render atau di dalam useMemo akan menghasilkan HTML server yang berbeda dengan klien dan merusak hidrasi React.
Masalahnya dulu
Di seksi Hero pembuka, saya menambahkan efek 12 hati yang melayang naik dengan posisi horizontal, ukuran, durasi, waktu tunda, dan goyangan kiri-kanan (drift) yang acak agar terlihat alami. Saat awalnya nilai-nilai Math.random() tersebut dibungkus di dalam useMemo, Next.js langsung melempar peringatan Hydration Mismatch karena angka acak yang dihasilkan saat pre-render di server berbeda dengan angka acak saat hidrasi di peramban.
Menunda pembangkitan nilai acak ke fase pasca-mount
Jejak eksperimen tersebut masih terlihat di komentar kode HomePage.tsx. Solusi yang bersih dan tahan banting adalah menginisialisasi daftar partikel sebagai array kosong useState<Heart[]>([]), lalu membangkitkan ke-12 objek partikel beserta nilai Math.random()-nya di dalam useEffect. Dengan pola ini, baik server maupun render pertama di klien sama-sama menghasilkan null, lalu sesaat setelah komponen terpasang di DOM klien, partikel hati mulai melayang dengan mulus.
Segala sesuatu yang bergantung pada Math.random() atau ukuran layar peramban adalah urusan klien setelah proses hidrasi selesai.
Implementasi di ngodingan ini
Berikut implementasi komponen FloatingHearts di src/pages/HomePage.tsx yang aman dari masalah hidrasi SSR:
function FloatingHearts() {
const [hearts, setHearts] = useState<Heart[]>([]);
// Generate random hanya di client (setelah mount) untuk menghindari hydration mismatch
useEffect(() => {
setHearts(
[...Array(12)].map((_, i) => ({
id: i,
left: `${5 + Math.random() * 90}%`,
size: `${14 + Math.random() * 14}px`,
duration: 4 + Math.random() * 5,
delay: Math.random() * 6,
drift: (Math.random() - 0.5) * 120,
yEnd: -(300 + Math.random() * 250),
}))
);
}, []);
if (hearts.length === 0) return null;
// ... render motion.div untuk tiap item heart
}Jebakan dan detail yang harus dijaga
- Mengira direktif "use client" di baris paling atas file berarti komponen tidak dijalankan di server. Di Next.js App Router, Client Component tetap di-pre-render di server pada muatan halaman awal.
- Memberikan keyframe array pada properti x ([0, h.drift * 0.4, h.drift, h.drift * 0.6, 0]) dan rotate ([0, -10, 8, -5, 0]) menciptakan gerakan mengayun seperti kelopak yang tertiup angin, bukan sekadar naik lurus ke atas.
- Menambahkan class pointer-events-none dan select-none pada partikel memastikan elemen dekoratif tidak pernah menghalangi sentuhan jari pengguna saat ingin menggulir layar.