Render mismatch terjadi ketika markup yang dihitung di server berbeda dengan yang dihasilkan saat klien melakukan hydration. Pada komponen React SSR yang memakai state lokal, mismatch sering muncul karena nilai awal state yang dipakai server tidak sama dengan nilai mutakhir saat klien dijalankan. Untuk mencegah flash UI dan kesalahan konsistensi, kita perlu mendiagnosa perbedaan ini secara sistematis dan menyeimbangkan state server dengan state klien.

Langkah awal diagnosis adalah mencatat titik perubahan state lokal yang mengandalkan data client-only (misalnya waktu lokal, status media query, atau nilai input yang just-in-time). Jangan menunggu hasil akhir; catat saat komponen pertama kali dirender server dan saat hydration klien dimulai. Paket log sederhana atau flag debugging bisa membantu memperlihatkan nilai apa yang dipakai pada kedua fase.

1. Observasi dan pencatatan nilai state lokal

Gunakan logging terstruktur agar membandingkan nilai state server dan klien lebih mudah. Contoh pendekatan:

const PageComponent = ({ initialValue }) => {
  const [localValue, setLocalValue] = useState(() => initialValue);

  useEffect(() => {
    console.log('hydration: localValue', localValue);
  }, [localValue]);

  return <div>{localValue}</div>;
};

Catatan: jangan panggil console.log di level yang menyebabkan rendering tambahan; gunakan useEffect agar hanya terjadi pada fase klien. Bandingkan log ini dengan nilai yang dicetak di server (misalnya dengan middleware logging saat render SSR) untuk melihat perbedaan langsung.

Selain log, React Developer Tools dan DevTools Profiler bisa membantu melihat apakah terdapat rendering ekstra atau state yang diperbarui segera setelah hydration.

2. Profiling hydration untuk mendeteksi mismatch

React Profiler dapat memunculkan warna (warna merah/kuning) pada stage hydration ketika terjadi re-render tak perlu. Aktifkan profiler di panel React DevTools dan praktikkan langkah berikut:

  • Render halaman SSR dan biarkan klien melakukan hydration.
  • Perhatikan apakah komponen utama melakukan re-render segera setelah hydration selesai.
  • Jika re-render terjadi tapi tidak ada input pengguna, kemungkinan besar state lokal mengalami update otomatis karena nilai awal tidak cocok.

Profiling membantu menentukan apakah mismatch berasal dari komponen khusus atau pola state tertentu. Jika Anda melihat komponen rerender tetapi tidak ada event, berarti nilai default state berbeda antara client dan server.

3. Case study: state lokal ter-sync tanpa flash UI

Misalkan Anda punya komponen daftar yang menampilkan apakah user sudah memilih sebuah opsi secara lokal. Server tidak bisa mengetahui pilihan user, jadi state default harus disinkronkan saat hydration selesai.

function PreferenceBadge({ serverPreference }) {
  const [preference, setPreference] = useState(serverPreference);
  const [hydrated, setHydrated] = useState(false);

  useEffect(() => {
    setHydrated(true);
  }, []);

  useEffect(() => {
    if (!hydrated) return;
    const stored = localStorage.getItem('preference');
    if (stored && stored !== preference) {
      setPreference(stored);
    }
  }, [hydrated, preference]);

  return <span className={preference}>Preference: {preference}</span>;
}

Penjelasan:

  • State awal: dipasok dari server lewat serverPreference agar SSR tidak mengalami mismatch yang jelas.
  • UseEffect pertama: menandai bahwa hydration sudah selesai; ini mencegah update state sebelum klien siap.
  • UseEffect kedua: membaca value dari localStorage hanya setelah hydration selesai sehingga tidak memicu flash white screen karena state masih sinkron dengan server.

Dengan pendekatan ini, markup awal tetap sesuai server. Setiap penyesuaian dari client hanya terjadi setelah hydration, sehingga tidak ada mismatch yang memicu warning React.

4. Strategi menjaga state konsisten

Berikut beberapa strategi umum yang membantu mempertahankan konsistensi state:

  • Pisahkan state yang hanya hidup di client: gunakan useEffect untuk inisialisasi state berdasarkan API browser (seperti ukuran viewport) dan pastikan nilai default server relevan atau netral.
  • Gunakan data serializable dari server: hindari memuat objek yang diproses client secara langsung—boleh menambahkan flag isClient.
  • Berikan fallback yang stabil: jika nilai akhir bergantung pada perhitungan client, pastikan server memberikan fallback tetap agar keduanya sejajar sebelum client menentukan hasil final.

Trade-off: jika Anda menunda update sampai after-hydration, pengguna mungkin melihat versi default sebentar sebelum state aktual muncul. Solusi terbaik adalah memilih default yang tidak terlalu jauh berbeda dengan nilai runtime atau mengganti placeholder dengan skeleton UI.

5. Debugging dan kesalahan umum

Kesalahan yang sering terjadi:

  • Memodifikasi DOM dalam rendering server: hindari penggunaan API browser saat render SSR karena akan berbeda dengan klien.
  • Tidak mengecek sumber data state: local state kadang berasal dari localStorage, cookies, atau preferensi user yang hanya tersedia di client—tandai dengan useEffect.
  • Konsistensi props: jika props berubah antara server dan client, pastikan data yang diberikan ke komponen sama keduanya atau siapkan fallback dalam useState.

Gunakan logging untuk melihat urutan render di server dan klien. Misalnya:

console.log('[server]', componentId, JSON.stringify(props));
console.log('[client hydration]', componentId, localValue);

Logs ini membantu tracking apakah komponen menerima props berbeda saat masuk ke klien, lalu memicu render ulang.

6. Kesimpulan dan langkah berikutnya

Mendiagnosa render mismatch pada React SSR dengan state lokal membutuhkan observasi konkrit terhadap log render, profiling hydration, serta pola state yang memastikan server dan klien berada dalam kesepakatan awal. Kombinasikan fallback state instan, syncing setelah hydration, dan logging terarah agar mismatch dapat diidentifikasi dan diperbaiki tanpa menimbulkan flash UI. Periksa setiap komponen yang menggunakan data client-only, dan selalu pastikan nilai default server kompatibel dengan perilaku client sesaat setelah hydration.