HydrationNext.jsReactHydration8 mnt baca

Memperbaiki Text Content Did Not Match Server di Next.js

Error hydration klasik: HTML dari server berbeda dengan hasil render client, penyebab umum dan polanya.

1 · Gejala & Log Pesan Error

error.log
Error: Text content does not match server-rendered HTML.

Warning: Text content did not match. Server: "Selamat pagi"
Client: "Selamat malam"

    at throwOnHydrationMismatch (react-dom.development.js)
    at tryToClaimNextHydratableInstance ...

This error happened while generating the page. Any console logs
will be displayed in the terminal window.

2 · Akar Penyebab Masalah

Server dan Client Me-render Hal yang Berbeda

Hydration = React "menempelkan" interaktivitas ke HTML yang sudah di-render server. Ia mengasumsikan output render pertama client identik dengan HTML server. Kalau berbeda, React protes.

Penyebab Paling Umum

  1. Nilai acak / waktu saat render: new Date(), Math.random(), atau Date.now() menghasilkan nilai berbeda di server vs client.
  2. Browser-only API saat render: window.innerWidth, localStorage. Tidak ada di server, menghasilkan undefined lalu berubah di client.
  3. Ekstensi browser yang menyuntik DOM (ad-blocker, translator). Di luar kontrol kode, tapi sering jadi tersangka.

3 · Solusi & Diff Kode

✕ Sebelum (bermasalah)

before
// ❌ Berbeda tiap render → hydration mismatch
export default function Greeting() {
  const hour = new Date().getHours(); // server: 08, client: 20?
  return <p>{hour < 12 ? "Selamat pagi" : "Selamat malam"}</p>;
}

✓ Sesudah (diperbaiki)

after
// ✔ Tunda ke client lewat useEffect + state
"use client";
import { useEffect, useState } from "react";

export default function Greeting() {
  const [hour, setHour] = useState<number | null>(null);

  useEffect(() => {
    setHour(new Date().getHours()); // hanya jalan di client
  }, []);

  if (hour === null) return <p>Memuat...</p>; // sama di server & client
  return <p>{hour < 12 ? "Selamat pagi" : "Selamat malam"}</p>;
}

Langkah Perbaikan

  1. Identifikasi sumber perbedaan: cari Date, Math.random(), atau akses window/localStorage langsung di body render.
  2. Pindahkan ke useEffect: render pertama harus deterministik (sama di server & client), nilai dinamis diisi setelah mount.
  3. Alternatif: bungkus komponen browser-only dengan dynamic(() => import('./Widget'), { ssr: false }). Komponen tidak di-render server sama sekali.
  4. Untuk teks tanggal/waktu: format di server dengan timezone eksplisit, atau tampilkan skeleton saat hour === null seperti contoh di atas.