Render mismatch di Next.js terjadi ketika markup yang dihasilkan server berbeda dengan versi yang di-hydrate di browser, yang bisa menimbulkan warning, kehilangan interaksi, atau flash konten. Untuk mengatasinya, kita perlu memahami kapan dan kenapa state sisi klien berubah saat proses hydration berlangsung, lalu menyelaraskannya dengan markup server.

Masalah utamanya biasanya berasal dari pemakaian state yang tidak deterministik—misalnya tanggal lokal atau akses window—yang menghasilkan DOM berbeda setelah SSR/SSG selesai. Artikel ini membahas akar penyebab, contoh konkret render mismatch, langkah debugging, dan cara memanfaatkan useEffect, useMemo, dynamic import, atau Suspense untuk menjaga state awal tetap konsisten.

Mengapa Render Mismatch Terjadi di Next.js

Next.js memanfaatkan SSR atau SSG untuk merender markup awal di server. Ketika browser menerima HTML tersebut, React akan mencoba hydrate ulang DOM berdasarkan state awal client. Apabila state awal berbeda (misalnya karena perhitungan waktu lokal, pengambilan data yang berbeda, atau state yang hanya ada di client), React akan mendeteksi mismatch dan memunculkan warning seperti "Text content did not match".

Contoh umum:

export default function Greeting() {
  const now = new Date().toLocaleDateString();

  return 

Hari ini tanggal {now}

; }

Markup server menggunakan zona waktu server, sementara client bisa punya zona waktu berbeda; setelah hydration, React melihat bahwa teks telah berubah lalu menulis ulang konten dan menampilkan warning. Penyebab lain bisa berupa state yang bergantung pada window, nilai random, atau data yang hanya tersedia saat client render.

Waktu Perbedaan State di Client

State yang hanya tersedia setelah DOM siap

State seperti ukuran jendela, lokasi scroll, atau status login yang dibaca dari localStorage hanya bisa ditentukan setelah komponen dirender di client. Jika state tersebut langsung digunakan saat render pertama, markup server (yang tidak memiliki akses ke API browser) akan berbeda.

Solusi: gunakan useEffect atau useLayoutEffect untuk menginisialisasi state setelah proses hydration selesai, lalu render placeholder deterministik selama server build.

State yang berubah tanpa kontrol

Penggunaan nilai random, Date.now(), atau data dari API yang dipanggil di client dapat menyebabkan setiap render client memiliki nilai berbeda dibanding server. Agar state tetap deterministik, hindari menjalankan kode non-deterministik saat render server, atau pastikan nilai awal konsisten dengan fallback yang di-render di server.

Strategi Menjaga Hydration Konsisten

Gunakan useMemo dan state awal deterministik

Untuk kasus tanggal, kita bisa memecahnya menjadi state yang di-hit hanya di client dan placeholder yang sama di server:

import { useEffect, useMemo, useState } from 'react';

export default function Greeting() {
  const [clientNow, setClientNow] = useState(null);
  const serverPlaceholder = useMemo(() => 'Loading tanggal...', []);

  useEffect(() => {
    setClientNow(new Date().toLocaleDateString());
  }, []);

  return 

Hari ini tanggal {clientNow ?? serverPlaceholder}

; }

Server selalu merender placeholder, sementara client mengganti dengan nilai aktual setelah hydration, tanpa memicu mismatch karena placeholder tidak berbeda dengan markup server.

Dynamic import atau Suspense untuk modul berbasis window

Jika sebuah komponen bergantung pada API browser, gunakan next/dynamic dengan ssr: false untuk hanya merendernya di client:

import dynamic from 'next/dynamic';

const WindowMetrics = dynamic(() => import('../components/WindowMetrics'), {
  ssr: false,
});

export default function Dashboard() {
  return (
    

Statistik

); }

Pendekatan ini mencegah Next.js mencoba merender komponen tersebut di server sehingga tidak ada mismatched markup.

Suspense untuk data fetching client-only

State yang mengandalkan data client (misalnya dari API lokal) bisa ditangani dengan Suspense atau fallback yang sama persis di server dan client. Pastikan fallback tidak bergantung pada data yang berbeda, lalu gunakan useEffect untuk mengupdate konten setelah data siap.

Contoh Kasus Render Mismatch dan Solusinya

Kasus: komponen menampilkan waktu lokal dan user ID yang dibaca dari localStorage.

export default function UserInfo() {
  const [userId, setUserId] = useState(null);

  useEffect(() => {
    setUserId(localStorage.getItem('userId'));
  }, []);

  return (
    

Halo {userId ?? 'pengguna'}, sekarang pukul {new Date().toLocaleTimeString()}

); }

Markup server menampilkan "pengguna" dan waktu server, sedangkan client berubah segera setelah useEffect dieksekusi, menyebabkan warning.

Perbaikan: tambahkan placeholder deterministik dan pisahkan bagian yang hanya diinisialisasi di client.

export default function UserInfo() {
  const [userId, setUserId] = useState(null);
  const [timeReady, setTimeReady] = useState(false);

  useEffect(() => {
    setUserId(localStorage.getItem('userId'));
    setTimeReady(true);
  }, []);

  return (
    

Halo {userId ?? 'pengguna'}, sekarang pukul {timeReady ? new Date().toLocaleTimeString() : '—'}

); }

Dengan cara ini server dan client awalnya menampilkan teks sama, lalu client mengganti hanya setelah state siap.

Debugging Render Mismatch

  • React DevTools: lihat apakah state/props berubah saat hydration. Jika tree di-render ulang, perhatikan bagian mana yang berbeda.
  • Console warning: React akan memberikan pesan detail seperti "Hydration failed" atau "Text content did not match". Periksa elemen yang disebutkan dan identifikasi nilai yang berbeda.
  • Next.js dev overlay: overlay menampilkan error hydration dengan stack trace. Periksa apakah komponen menggunakan nilai random atau API browser saat render server.
  • Logisasi di server/client: tambahkan console.log atau breakpoint untuk melihat nilai saat render server vs client. Pastikan tidak log nilai unik (misalnya ID baru).

Checklist Praktis untuk Hydration Konsisten di Produksi

  • Pastikan semua state awal deterministik atau memiliki fallback identik di server dan client.
  • Gunakan useEffect/useLayoutEffect untuk inisialisasi state yang bergantung pada browser API.
  • Konsolidasikan nilai non-deterministik (tanggal, random) dengan placeholder yang sama saat SSR.
  • Pisahkan komponen window-only dengan next/dynamic dan ssr: false.
  • Periksa console untuk warning hydration dan gunakan DevTools untuk membandingkan tree server/client.
  • Jaga agar Suspense/fallback menampilkan konten konsisten antara render server dan client.
  • Uji dengan next dev dan build produksi untuk memastikan warning tidak muncul setelah penerapan.