Hydration mismatch terjadi ketika representasi Virtual DOM yang dihasilkan React di browser tidak identik dengan HTML hasil rendering server (SSR). Pada platform rilis sepatu berkala (seperti sneakerweb.org), jadwal peluncuran bersifat mutlak. Kesalahan implementasi date formatting sering memicu React error #418 atau #423 (Text content does not match server-rendered HTML).

Akar Masalah: Mengapa Intl.DateTimeFormat Naif Memecahkan Hydration

Server production (seperti runtime Node.js di server AWS atau Edge workers) umumnya berjalan di lingkungan sistem dengan zona waktu UTC dan locale fallback en-US. Sebaliknya, browser pembeli mengeksekusi JavaScript menggunakan zona waktu lokal sistem operasi (misal: Asia/Jakarta atau UTC+7) serta preferensi bahasa pengguna (misal: id-ID).

Ketika kode berikut dieksekusi secara langsung di dalam komponen render:

// Contoh implementasi naif yang memicu mismatch
export function SneakerCard({ dropTimestamp }: { dropTimestamp: number }) {
  const formattedDate = new Intl.DateTimeFormat("id-ID", {
    dateStyle: "medium",
    timeStyle: "short",
  }).format(new Date(dropTimestamp));

  return (
    <div className="card">
      <h3>Retro High OG</h3>
      <span>Drop: {formattedDate}</span>
    </div>
  );
}

Server menghasilkan teks dengan offset jam UTC, sedangkan client menghasilkan teks dengan offset jam lokal browser. React membandingkan kedua string ini pada tahap hidrasi awal. Perbedaan karakter menyebabkan pembatalan patch DOM parsial dan memaksa React merender ulang subtree dari sisi client, menurunkan efisiensi First Meaningful Paint (FMP).

Solusi 1: Two-Pass Rendering dengan useEffect

Pendekatan ini menunda rendering string lokal hingga siklus render client selesai. Server hanya merender fallback statis deterministik (misalnya string ISO atau placeholder), lalu memperbaruinya pada render kedua di client.

'use client';

import { useState, useEffect } from 'react';

interface Props {
  dropTimestamp: number;
}

export function TwoPassDropDate({ dropTimestamp }: Props) {
  const [formatted, setFormatted] = useState<string | null>(null);

  useEffect(() => {
    // Dijalankan hanya di client setelah hidrasi awal aman
    const localized = new Intl.DateTimeFormat('id-ID', {
      dateStyle: 'medium',
      timeStyle: 'short',
    }).format(new Date(dropTimestamp));
    
    setFormatted(localized);
  }, [dropTimestamp]);

  // ponytail: rendering placeholder statis pada render pertama untuk mencegah mismatch
  if (!formatted) {
    return <span>Loading schedule...</span>;
  }

  return <span>Drop: {formatted}</span>;
}

Trade-off: Terjadi Layout Shift (CLS) ringan saat teks fallback digantikan tanggal final. Gunakan teknik ini jika akurasi teks lokal lebih diprioritaskan daripada pergeseran layout kecil.

Solusi 2: Pemanfaatan suppressHydrationWarning

Jika konten tanggal terisolasi dan Anda menerima perbedaan visual antara server dan client tanpa perlu merender ulang keseluruhan komponen, React menyediakan atribut escape hatch: suppressHydrationWarning.

interface Props {
  dropTimestamp: number;
}

export function IsolatedDropDate({ dropTimestamp }: Props) {
  const dateStr = new Intl.DateTimeFormat('id-ID', {
    dateStyle: 'medium',
    timeStyle: 'short',
  }).format(new Date(dropTimestamp));

  return (
    <span suppressHydrationWarning>
      {dateStr}
    </span>
  );
}

Batasan: Atribut ini hanya bekerja pada kedalaman satu level teks elemen yang bersangkutan. Ini tidak menyelesaikan akar masalah perbedaan waktu awal yang terlihat mata saat transisi hidrasi selesai, namun mencegah React membuang pohon DOM di sekitarnya.

Solusi 3: Pre-formatted String Melalui Server Context / Headers

Cara paling optimal untuk performa dan SEO tanpa layout shift adalah mengirim preferensi timezone pengguna ke server melalui HTTP cookies atau reverse proxy header (seperti Cloudflare CF-IPTimezone), lalu memformat tanggal langsung pada Server Component.

// app/sneakers/page.tsx (React Server Component)
import { cookies } from 'next/headers';

export default async function SneakerDropPage() {
  const cookieStore = await cookies();
  const userTimezone = cookieStore.get('user-tz')?.value || 'UTC';
  const dropTimestamp = 1735689600000; // 1 Jan 2025 00:00:00 UTC

  const formattedDate = new Intl.DateTimeFormat('id-ID', {
    timeZone: userTimezone,
    dateStyle: 'medium',
    timeStyle: 'short',
  }).format(new Date(dropTimestamp));

  return (
    <div>
      <h1>Sneaker Launch Calendar</h1>
      <p>Waktu Rilis: {formattedDate}</p>
    </div>
  );
}

Klien menyetel cookie user-tz sekali via JavaScript (menggunakan Intl.DateTimeFormat().resolvedOptions().timeZone). Server menghasilkan string yang presisi sejak byte pertama diterima browser, menghasilkan zero-mismatch dan zero-layout shift.