Render mismatch terjadi ketika HTML yang dikirim server berbeda dengan tree React yang dihidupkan pada sisi klien. Dalam Next.js, perbedaan ini umum terjadi ketika nilai state berubah sebelum proses hydration selesai, misalnya karena data waktu nyata, parameter query, atau nilai lokal yang dibaca secara client-only. Artikel ini langsung menjelaskan akar masalah, bagaimana mendeteksinya, dan apa saja cara praktis memperbaikinya.

Bagaimana Render Mismatch Terjadi di App Router

Ketika Next.js merender halaman dengan App Router, konten awal dibuat di server dan dikirim sebagai HTML statis. React kemudian harus menghidupkan (hydrate) markup tersebut tanpa mengubah struktur DOM. Jika state awal di client berbeda dengan data yang digunakan server, React akan memberi peringatan “Hydration failed because the initial UI does not match what was rendered on the server.”

Contoh nyata: Anda menampilkan waktu lokal pengguna di komponen server. Versi server menggunakan waktu saat permintaan diterima, tetapi begitu JavaScript client berjalan, state useState diinisialisasi ulang dengan waktu terbaru. Jika perubahan terjadi sebelum hydration selesai, Next.js mencatat mismatch.

Kasus konkret: data waktu nyata vs server render

Misalkan komponen berikut berada di app/page.tsx dengan App Router.

export default function Page() {
  const [now, setNow] = React.useState('');
  React.useEffect(() => {
    const iso = new Date().toISOString();
    setNow(iso);
  }, []);

  return (
    <div>
      <p>Waktu dari client: {now || 'memuat...'}</p>
    </div>
  );
}

Saat server merender, now kosong. Saat client selesai hydrate, now langsung diisi dengan timestamp baru, menyebabkan mismatch jika React sudah menempelkan markup. Dalam kasus ini, Anda perlu menjaga agar DOM tidak berubah saat hydration berlangsung.

Langkah Debugging Render Mismatch

Menemukan sumber mismatch butuh pendekatan sistematis:

  • Log state dari server dan client. Pada halaman App Router, gunakan log di server component (misalnya di page.tsx) dan di client component untuk melihat perbedaan nilai awal. Gunakan console.log di useEffect agar hanya dijalankan setelah hydration.
  • Bandingkan snapshot DOM. Jalankan next dev, lalu buka halaman di browser dan ambil document.documentElement.innerHTML sebelum React hydrate (dengan breakpoint atau setTimeout pendek). Setelah hydration, ambil snapshot lagi. Perbedaan struktur atau atribut mengungkapkan lokasi yang tidak sesuai.
  • React DevTools. Gunakan React DevTools untuk memeriksa props/state komponen yang bermasalah. Pastikan props yang diteruskan ke child komponen sama antara server dan client. Jika props berbeda, maka Anda tahu mana yang perlu disinkronkan.
  • Observasi perbedaan props. Jika Anda mengirim props dari layout atau server component, tambahkan debugger/console saat menerima props di client component untuk memastikan nilainya konsisten. Gunakan console.trace bila perlu untuk menelusuri stack pemanggilan.
  • Gunakan tooling Next.js. Jalankan next dev --inspect atau tambahkan flag NEXT_RUNTIME=nodejs bila Anda mengamati behavior spesifik runtime. Mode development Next.js sudah mencetak warning mismatch di console browser; pastikan Anda tidak mematikan warning tersebut.

Penyebab Umum Render Mismatch

Data real-time yang diinisialisasi client-side

Kalau Anda mengandalkan data real-time (misalnya status WebSocket, nilai lokal dari localStorage, atau waktu sekarang), jangan langsung memasukkannya ke markup sebelum hydration selesai. Gunakan pattern berikut:

function ClientTimestamp() {
  const [timestamp, setTimestamp] = React.useState(null);

  React.useEffect(() => {
    setTimestamp(new Date().toLocaleTimeString());
  }, []);

  if (timestamp === null) {
    return <span data-loading>Memuat waktu...</span>;
  }

  return <span>{timestamp}</span>;
}

Dengan menampilkan placeholder, markup server tidak berubah saat client selesai hydrate.

Query parameter atau cookie yang berubah antar request

Ketika Anda membaca parameter query di client dan langsung memengaruhi render, pastikan nilai tersebut sudah tersedia di server rendering pertama kali. Jika tidak, gunakan middleware per-request untuk menyimpan nilai yang konsisten.

Strategi Mitigasi dan Pattern

Sinkronisasi data lewat middleware per permintaan

Gunakan middleware Next.js untuk menyisipkan data stabil ke tiap permintaan, lalu kirimkan ke page via context.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const region = request.headers.get('x-region') ?? 'id';
  const res = NextResponse.next();
  res.cookies.set('region', region);
  return res;
}

// Di server component
export default function Page({ params }: { params: { slug: string } }) {
  const region = cookies().get('region')?.value ?? 'id';
  // region sama saat SSR dan client
}

Dengan begitu, nilai yang dipakai server dan client tetap sinkron karena berasal dari header/cookie yang sama.

Guard state client-only

Untuk state yang hanya boleh eksis di client (localStorage, WebSocket), bungkus perubahan dalam useEffect dan berikan fallback markup statis saat state belum tersedia. Hindari memodifikasi DOM di luar React sebelum hydration selesai.

Strategi fallback saat hydration gagal

Jika mismatch terus terjadi meski sudah mencoba menyinkronkan data, pertimbangkan fallback berikut:

  • Gunakan suppressHydrationWarning hanya untuk atribut tertentu (misal date string) dengan catatan perbedaannya tidak menimbulkan kesalahan logika.
  • Kunci rendering client-only dengan flag: beri prop data-hydrated di React yang hanya muncul setelah useEffect selesai, sehingga Anda tahu bahwa markup berubah karena intentional update.
  • Kalau mismatch sangat sulit ditangani, pertimbangkan rerender dari client sepenuhnya dengan menambahkan 'use client' dan menghindari markup server yang rentan berubah.

Dalam semua kasus, evaluasi trade-off performa vs konsistensi. Misalnya, mengubah komponen menjadi client-only memperbesar bundle, tapi mengurangi mismatch. Alternatifnya, Anda bisa memisahkan bagian dinamis ke komponen kecil agar hanya bagian itu yang dihidupkan ulang.

Catatan Tambahan

Debugging render mismatch memerlukan pengamatan terperinci terhadap data yang mengalir antara server dan client. Gunakan kombinasi log, snapshot DOM, dan React DevTools. Pastikan juga Next.js diatur dalam mode development ketika mencari warning, karena tooling production tidak menampilkan mismatch. Setelah menemukan penyebabnya, pilih mitigasi yang paling sedikit menambah kompleksitas state management.

Dengan pendekatan sistematis tersebut, Anda bisa mengurai mismatch, mengurangi peringatan hydration, dan menjaga UX tetap stabil.