Hydration mismatch terjadi ketika tree DOM hasil render server berbeda dengan tree DOM yang dibentuk oleh runtime React pada fase hidrasi awal di browser. Masalah ini sering muncul saat mengintegrasikan mock service atau local cloud emulation (misalnya AWS LocalStack, Firebase Emulator, atau IndexedDB mock adapter). Server Node.js merender initial state statis tanpa akses ke local browser storage atau local emulator auth, sementara client script langsung mengeksekusi mock state lokal saat bootstrap.
Akar Masalah: State Discrepancy Lintas Runtime
Server-Side Rendering (SSR) mengharuskan server dan client mengeksekusi render pertama menggunakan data yang sepenuhnya identik. Pada environment development yang menggunakan local cloud blocks atau mock adapter browser:
- Server Runtime: Berjalan di Node.js. Tidak memiliki akses ke objek
window,localStorage, cookie browser yang belum tersinkronisasi, atau endpoint emulator lokal yang hanya diikat ke loopback client. Komponen menghasilkan markup dengan nilai default atau fallback state. - Client Runtime: Menginisialisasi mock adapter sesaat sebelum atau saat hydration. Adapter membaca status sesi lokal atau flag storage emulator, langsung mengubah komponen menjadi status terotentikasi atau data ter-mock sebelum hidrasi selesai.
Perbedaan output HTML ini memicu React membuang cabang DOM server dan menampilkan warning Text content does not match server-rendered HTML atau Hydration failed because the initial UI does not match what was rendered on the server.
Solusi 1: Two-Pass Rendering dengan Mount Gating
Pendekatan ini memaksa komponen merender representasi yang konsisten antara server dan client pada pass pertama. State lokal dari emulator baru dievaluasi setelah event mount browser terjadi.
import { useState, useEffect, type ReactNode } from 'react';
interface LocalEmulatorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
}
export function LocalEmulatorBoundary({
children,
fallback = <div>Menghubungkan ke local emulator...</div>
}: LocalEmulatorBoundaryProps) {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
// ponytail: flag mount sederhana; ganti syncExternalStore jika butuh reactivity intensif
setIsMounted(true);
}, []);
if (!isMounted) {
return <>{fallback}</>;
}
return <>{children}</>;
}Komponen di atas memastikan server dan client pertama kali menghasilkan HTML dari fallback. Komponen anak yang memanggil API mock adapter lokal baru dieksekusi setelah hidrasi selesai sempurna.
Solusi 2: Dynamic Import dengan ssr: false
Jika satu modul utuh sepenuhnya bergantung pada mock service lokal browser (misalnya debug dashboard atau widget emulasi AWS S3/Cognito), isolasi komponen tersebut agar tidak dirender sama sekali di sisi server.
import dynamic from 'next/dynamic';
const LocalStorageEmulatorViewer = dynamic(
() => import('@/components/debug/EmulatorViewer'),
{
ssr: false,
loading: () => <p role="status">Memuat adapter debug...</p>,
}
);export default function DebugPage() {
return (
<main>
<h2>Panel Debug Emulasi Cloud</h2>
<LocalStorageEmulatorViewer />
</main>
);
}Solusi 3: Deterministic Initial State Injection
Jika komponen memerlukan data emulator langsung sejak server render, kirim state awal secara eksplisit dari server melalui props atau konteks. Hindari pembacaan langsung global variable atau mock client state pada root scope komponen.
interface ConfigProps {
emulatorEndpoint: string;
isMockActive: boolean;
}
// Initial state deterministik dioper langsung dari SSR context/page props
export default function ServiceStatus({ emulatorEndpoint, isMockActive }: ConfigProps) {
return (
<div className="status-card">
<span>Target Runtime: {emulatorEndpoint}</span>
<span>Mock Status: {isMockActive ? 'Active' : 'Bypass'}</span>
</div>
);
}Verifikasi Konsistensi DOM
Verifikasi bahwa markup server dan client cocok melalui langkah berikut:
- Periksa DevTools Console: Identifikasi node DOM spesifik yang dilaporkan oleh React hydration log. Perhatikan tag atau teks pembeda.
- Disable JavaScript di Browser: Buka tab Network/Inspect, nonaktifkan JS, lalu muat ulang halaman. Ini adalah DOM asli hasil SSR server.
- Bandingkan Server Response vs DOM Tree: Jalankan
curl -s http://localhost:3000/path | grep 'selector'di terminal dan bandingkan dengan node yang muncul di tab Elements browser setelah script berjalan. Keduanya harus identik sebelum interaksi user terjadi.
Catatan: Atribut suppressHydrationWarning hanya boleh digunakan untuk data yang tidak terkontrol (seperti timestamp dinamis), bukan untuk menutupi perbedaan arsitektural state antara mock runtime dan server.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!