Hydration mismatch pada aplikasi Server-Side Rendering (SSR) seperti Next.js terjadi ketika pohon DOM hasil render server berbeda dengan representasi virtual DOM pada client. Dalam domain aplikasi perbankan atau akuntansi, inkonsistensi ini memicu layout shift, rusaknya struktur tabel, hingga ancaman fatal salah baca nominal uang.

Sebagaimana diulas pada catatan sejarah ekonomi di The Daily Economy, ledger kuno bangsa Sumeria diukir pada lempengan tanah liat sebagai representasi transaksi abadi yang immutable. Data finansial historis modern memegang prinsip serupa: angka, debit, kredit, dan waktu tidak boleh berubah begitu tercatat. Masalah timbul ketika representasi data abadi tersebut harus diproyeksikan melalui runtime yang berbeda antara Node.js di server dan browser pengguna.

Akar Masalah Hydration Error pada Data Finansial

Penyebab utama inkonsistensi rendering pada tabel ledger terbagi dalam tiga faktor teknis:

  • Perbedaan Locale Intl.NumberFormat: Server container kerap berjalan dengan environment default en-US atau POSIX, sementara browser client menggunakan locale pengguna (misal id-ID). Angka 1000000 dirender sebagai $1,000,000.00 di server dan Rp 1.000.000,00 di browser.
  • Timezone Offset Historis: Server berjalan pada UTC (+00:00), sementara client berjalan pada timezone lokal (misal WIB +07:00). Timestamp ISO 2024-03-31T22:00:00Z dirender server pada tanggal 31 Maret, namun browser menampilkan tanggal 1 April.
  • Mutasi State Lokal Saat Mount: Pembacaan properti client-side seperti localStorage atau konversi dinamis langsung di dalam body komponen sebelum mount selesai.

Strategi Mengatasi Hydration Mismatch

1. Two-Pass Rendering dengan Custom Hook

Pendekatan ini menunda rendering elemen dinamis berbasis locale browser hingga proses hidrasi awal selesai secara deterministik.

import { useState, useEffect } from 'react';

export function useHydrated() {
  const [hydrated, setHydrated] = useState(false);
  useEffect(() => {
    setHydrated(true);
  }, []);
  return hydrated;
}

2. Format Deterministik dan Atribut suppressHydrationWarning

Untuk teks nominal sederhana, passing locale eksplisit dari server adalah pilihan teringan. Jika string teks leaf node tetap berisiko mismatch akibat perbedaan engine browser, gunakan suppressHydrationWarning secara terbatas pada tag target.

// Komponen LedgerAmount
interface Props {
  amount: number;
  currency: string;
  locale?: string;
}

export function LedgerAmount({ amount, currency, locale = 'en-US' }: Props) {
  const formatted = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
  }).format(amount);

  return <span suppressHydrationWarning>{formatted}</span>;
}
suppressHydrationWarning hanya bekerja satu level ke bawah pada elemen teks native. Atribut ini tidak menyembunyikan error jika struktur tag anak berbeda.

3. Arsitektur Data Berbasis UTC Konsisten

Struktur data ledger yang dikirim dari server harus membawa string waktu deterministik. Format tampilan di-render sebagai UTC murni pada pass pertama, atau gunakan format ISO standar sebelum dikonversi di client.

interface LedgerEntry {
  id: string;
  timestamp: string; // ISO 8601 UTC
  amount: number;
}

export function LedgerRow({ entry }: { entry: LedgerEntry }) {
  const isHydrated = useHydrated();

  // Server & First-pass Client: Pakai representasi UTC deterministik
  // Client Hydrated: Pakai locale lokal pengguna
  const displayDate = isHydrated
    ? new Date(entry.timestamp).toLocaleString()
    : new Date(entry.timestamp).toISOString().replace('T', ' ').slice(0, 19) + ' UTC';

  return (
    <tr>
      <td>{displayDate}</td>
      <td>{entry.id}</td>
      <td><LedgerAmount amount={entry.amount} currency="USD" /></td>
    </tr>
  );
}

Trade-offs dan Kesimpulan

Pola Two-Pass Rendering menambah satu siklus re-render di browser setelah mount, yang menimbulkan sedikit visual swap. Alternatif yang lebih optimal tanpa re-render adalah memaksakan locale deterministik (seperti format akuntansi baku ISO) langsung dari backend API, atau mendeteksi preferensi locale pengguna via HTTP Accept-Language headers / cookies pada level server middleware.