Cara Mengatasi Error “Hydration failed because the initial UI does not match” di Next.js [Panduan Terbaru 2026]

スポンサーリンク

Cara Mengatasi Error “Hydration failed because the initial UI does not match” di Next.js [Panduan Terbaru 2026]

Jika Anda sedang mengembangkan aplikasi dengan Next.js dan tiba-tiba muncul error “Hydration failed because the initial UI does not match what was rendered on the server”, Anda tidak sendirian. Error ini sangat sering terjadi di lingkungan React 19 dan Next.js 15 dengan App Router, dengan ribuan komentar di GitHub Discussions dan selalu menjadi trending di Stack Overflow. Artikel ini menyediakan panduan lengkap yang diperbarui untuk tahun 2026 untuk mengidentifikasi penyebab dan menerapkan solusi spesifik, dilengkapi dengan contoh kode nyata.

Apa Error Ini? Gejala yang Akan Anda Alami

Hydration Error di Next.js terjadi ketika HTML yang di-render di sisi server tidak cocok dengan HTML yang coba di-render oleh React di sisi client saat render awal.

Secara spesifik, pesan error berikut akan muncul di console pengembang browser Anda:

Unhandled Runtime Error
Error: Hydration failed because the initial UI does not match what was rendered on the server.

Anda mungkin juga melihat variasi berikut:

Error: Text content does not match server-rendered HTML.
Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

Ketika error ini terjadi, Next.js membuang hasil server-side rendering (SSR) dan me-render ulang seluruh halaman di sisi client. Hal ini menyebabkan beberapa konsekuensi serius:

  • Penurunan performa signifikan: Semua manfaat SSR hilang, membuat pemuatan halaman awal lebih lambat
  • Dampak negatif SEO: Crawler mesin pencari mungkin tidak menerima HTML yang benar
  • Pengalaman pengguna buruk: Terjadi kedipan layar (flash of content)
  • Pengalaman developer memburuk: Console dipenuhi error, memudahkan melewatkan masalah lain

Di Next.js 15 yang dikombinasikan dengan React 19, pemeriksaan konsistensi hydration menjadi lebih ketat, dan kasus yang sebelumnya hanya berupa peringatan kini diperlakukan sebagai error.

Penyebab Error Ini

Penyebab 1: Menggunakan API Khusus Browser (window / localStorage / document)

Penyebab paling sering adalah mereferensikan langsung API khusus browser seperti window, localStorage, dan document selama proses rendering. Objek-objek ini tidak ada di sisi server.

Di server, objek-objek ini bernilai undefined. Oleh karena itu, menggunakan cabang kondisional seperti typeof window !== 'undefined' dalam logika rendering menyebabkan output yang berbeda antara server dan client, memicu hydration error.

// SALAH: Ini menyebabkan hydration error
function MyComponent() {
  const isClient = typeof window !== 'undefined';
  return <div>{isClient ? 'Client' : 'Server'}</div>;
}

Penyebab 2: Nilai Non-deterministik Seperti Timestamp dan Angka Acak

Menggunakan fungsi yang mengembalikan nilai berbeda setiap kali dipanggil, seperti new Date() atau Math.random(), selama rendering menyebabkan ketidakcocokan antara output server dan client.

// SALAH: Timestamp berbeda antara server dan client
function Clock() {
  return <p>Waktu saat ini: {new Date().toLocaleTimeString()}</p>;
}

Selalu ada jeda waktu antara saat server merender dan saat client melakukan hydrate, sehingga waktu yang ditampilkan secara alami akan berbeda, menghasilkan error ketidakcocokan konten teks.

Penyebab 3: Struktur Nesting HTML yang Tidak Valid

Struktur nesting yang melanggar spesifikasi HTML (misalnya, menempatkan <div> di dalam tag <p>) menyebabkan parser HTML browser secara otomatis mengoreksi DOM, membuat struktur DOM yang berbeda dari HTML yang di-render di server.

// SALAH: <div> tidak bisa ditempatkan di dalam <p>
function BadNesting() {
  return (
    <p>
      Teks
      <div>Ini adalah nesting yang tidak valid</div>
    </p>
  );
}

Browser secara otomatis mengoreksi HTML yang tidak valid ini, menyebabkan ketidakcocokan antara HTML yang dikirim server dan DOM browser.

Penyebab 4: Ekstensi Browser yang Memodifikasi DOM

Ekstensi browser seperti Colorzilla dan Grammarly dapat menyuntikkan atribut atau elemen HTML ke dalam DOM halaman. Misalnya, Colorzilla menambahkan atribut cz-shortcut-listen="true" ke tag <body>. Ini dilaporkan sebagai masalah yang sangat menonjol di lingkungan Next.js 15 + React 19.

Penyebab 5: Script Pihak Ketiga dan Ketidakkonsistenan Konten CMS

Script eksternal (tag iklan, analytics, dll.) yang memodifikasi DOM, atau konten HTML yang diperoleh dari CMS yang mengandung struktur nesting tidak valid, dapat memicu hydration error.

Solusi 1: Isolasi Logika Khusus Client dengan Hook useEffect (Direkomendasikan)

Solusi yang paling efektif dan direkomendasikan adalah memindahkan logika yang bergantung pada browser ke dalam hook useEffect dan hanya menghasilkan nilai deterministik selama render awal.

Langkah 1: Implementasikan Pola Manajemen State Client

Pertama, definisikan State dengan nilai awal yang sama untuk server dan client, kemudian perbarui ke nilai sisi client dengan useEffect.

'use client';

import { useState, useEffect } from 'react';

function ThemeProvider({ children }) {
  // Gunakan nilai yang aman untuk server sebagai state awal
  const [theme, setTheme] = useState('light');
  const [mounted, setMounted] = useState(false);

  useEffect(() => {
    // Hanya akses localStorage di client
    const savedTheme = localStorage.getItem('theme') || 'light';
    setTheme(savedTheme);
    setMounted(true);
  }, []);

  // Sebelum mount, kembalikan output yang sama dengan server
  if (!mounted) {
    return <div data-theme="light">{children}</div>;
  }

  return <div data-theme={theme}>{children}</div>;
}

Langkah 2: Atur Timestamp dan Nilai Acak di dalam useEffect

'use client';

import { useState, useEffect } from 'react';

function Clock() {
  const [currentTime, setCurrentTime] = useState('');

  useEffect(() => {
    const updateTime = () => {
      setCurrentTime(new Date().toLocaleTimeString());
    };
    updateTime();
    const timer = setInterval(updateTime, 1000);
    return () => clearInterval(timer);
  }, []);

  // Tampilkan string kosong pada render awal (cocok dengan server)
  return <p>Waktu saat ini: {currentTime || 'Memuat...'}</p>;
}

Langkah 3: Buat Dapat Digunakan Ulang dengan Custom Hook

Jika Anda menggunakan pola ini di beberapa komponen, ekstrak ke custom hook.

'use client';

import { useState, useEffect } from 'react';

// Custom hook untuk mengelola status mount
function useHasMounted() {
  const [hasMounted, setHasMounted] = useState(false);
  useEffect(() => {
    setHasMounted(true);
  }, []);
  return hasMounted;
}

// Contoh penggunaan
function ClientOnlyComponent() {
  const hasMounted = useHasMounted();

  if (!hasMounted) {
    return <div>Memuat...</div>;
  }

  return <div>Lebar jendela: {window.innerWidth}px</div>;
}

Catatan Penting

  • useEffect tidak dieksekusi di sisi server, sehingga API browser dapat digunakan dengan aman di dalamnya
  • Tampilkan placeholder atau UI fallback selama render awal untuk menjaga pengalaman pengguna
  • Pola ini mungkin sebentar menampilkan placeholder, tetapi jauh lebih ringan daripada re-rendering seluruh halaman yang disebabkan oleh hydration error

Solusi 2: Nonaktifkan SSR dengan next/dynamic

Untuk komponen yang sulit ditangani dengan pola useEffect (misalnya, library pihak ketiga yang sangat bergantung pada API browser), menonaktifkan SSR sepenuhnya menggunakan next/dynamic efektif.

import dynamic from 'next/dynamic';

// Import dengan SSR dinonaktifkan
const MapComponent = dynamic(
  () => import('../components/Map'),
  {
    ssr: false,
    loading: () => <div className="map-placeholder">Memuat peta...</div>
  }
);

// Contoh penggunaan
function LocationPage() {
  return (
    <div>
      <h1>Lokasi Toko</h1>
      <MapComponent />
    </div>
  );
}

Metode ini sangat efektif dalam kasus berikut:

  • Library peta (Leaflet, Google Maps, dll.): Karena bergantung pada objek window
  • Editor teks kaya (Quill, TipTap, dll.): Karena langsung memanipulasi objek document
  • Library grafik (Chart.js, D3.js, dll.): Karena melakukan operasi DOM SVG atau Canvas

Namun, komponen dengan SSR dinonaktifkan tidak termasuk dalam HTML awal, jadi hindari ini untuk konten yang penting untuk SEO. Praktik terbaik adalah membatasi penggunaannya pada elemen interaktif selain konten utama (peta, grafik, editor, dll.).

Solusi 3: Perbaiki Nesting HTML dan Penggunaan suppressHydrationWarning yang Tepat

Sebagai solusi tingkat lanjut, Anda dapat memperbaiki struktur nesting HTML dan menggunakan suppressHydrationWarning untuk kasus yang tidak dapat dihindari.

Memperbaiki Nesting HTML yang Tidak Valid

Pertama, periksa apakah ada nesting HTML yang tidak valid. Berikut adalah pola tidak valid yang umum dan koreksinya:

// SALAH: <div> di dalam <p>
<p>Teks<div>Elemen blok</div></p>

// BENAR: Ubah ke <div>
<div>Teks<div>Elemen blok</div></div>

// SALAH: <a> di dalam <a>
<a href="/induk">
  Link induk
  <a href="/anak">Link anak</a>
</a>

// BENAR: Hapus nesting
<div>
  <a href="/induk">Link induk</a>
  <a href="/anak">Link anak</a>
</div>

Penggunaan Terbatas suppressHydrationWarning

Untuk elemen kecil di mana perbedaan nilai server-client tidak dapat dihindari (seperti tampilan timestamp), Anda dapat menggunakan suppressHydrationWarning.

// Gunakan hanya ketika perbedaan dapat diterima, seperti timestamp
<time suppressHydrationWarning>
  {new Date().toLocaleDateString()}
</time>

Catatan penting: suppressHydrationWarning hanya menyembunyikan error; tidak menyelesaikan masalah yang mendasarinya. Ini hanya berlaku untuk satu kedalaman text node, dan ketidakcocokan elemen anak tetap terdeteksi. Gunakan dengan hemat dan hanya ketika benar-benar diperlukan.

Menangani Ekstensi Browser

Jika ekstensi browser adalah penyebabnya, Anda dapat menambahkan suppressHydrationWarning ke tag <body>.

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html lang="id">
      <body suppressHydrationWarning>{children}</body>
    </html>
  );
}

Ini menekan peringatan yang disebabkan oleh atribut yang ditambahkan ekstensi ke body. Namun, ketidakcocokan pada anak langsung body tetap terdeteksi.

Cara Mencegah Error Ini

Untuk mencegah hydration error secara proaktif, terapkan langkah-langkah pencegahan ini dalam alur kerja pengembangan harian Anda.

1. Jaga Render Awal Tetap “Deterministik”

Prinsip terpenting adalah menjamin output yang identik untuk render awal server dan client. Nilai yang bergantung pada lingkungan (API browser, timestamp, angka acak, dll.) harus selalu ditempatkan di dalam useEffect.

2. Pisahkan dengan Jelas Server Components dan Client Components

Di App Router Next.js 15, komponen adalah Server Components secara default. Saat menggunakan API browser atau React hooks (useState, useEffect, dll.), selalu tambahkan direktif 'use client' di bagian atas file.

3. Manfaatkan Aturan ESLint

eslint-plugin-react mencakup aturan untuk mendeteksi nesting HTML yang tidak valid (seperti jsx-no-invalid-html-nesting). Integrasikan ke dalam pipeline CI Anda untuk pemeriksaan otomatis.

4. Pengujian Berkala

Uji secara berkala dalam mode incognito (private browsing) atau dengan ekstensi browser dinonaktifkan untuk mengisolasi error terkait ekstensi.

5. Desain Responsif Berbasis CSS

Alih-alih membagi markup berdasarkan ukuran viewport, gunakan CSS media queries atau display: none untuk mengalihkan visibilitas, menjaga struktur HTML konsisten antara server dan client.

// SALAH: Pembagian viewport di JS
{isMobile ? <MobileNav /> : <DesktopNav />}

// BENAR: Pergantian berbasis CSS
<MobileNav className="block md:hidden" />
<DesktopNav className="hidden md:block" />

Ringkasan

Hydration Error Next.js “Hydration failed because the initial UI does not match what was rendered on the server” terjadi karena ketidakcocokan antara SSR/SSG dan rendering sisi client, menjadikannya salah satu error yang paling sering ditemui dalam pengembangan Next.js.

Poin-poin Utama:

  • Inti masalah adalah “hasil render awal yang berbeda antara server dan client”
  • Penyebab paling umum: penggunaan langsung API khusus browser, rendering nilai non-deterministik, nesting HTML tidak valid
  • Solusi yang direkomendasikan: rendering bertahap dengan useEffect, nonaktifkan SSR dengan next/dynamic jika diperlukan
  • suppressHydrationWarning harus digunakan sebagai pilihan terakhir, dengan cakupan terbatas
  • Pencegahan: jaga render awal tetap deterministik, pisahkan dengan jelas Server/Client Components

Jika solusi-solusi ini tidak menyelesaikan masalah, periksa hal berikut:

  1. Perbarui Next.js dan React ke versi terbaru
  2. Hapus folder node_modules dan .next lalu build ulang
  3. Cari kasus serupa di GitHub Discussions Next.js (https://github.com/vercel/next.js/discussions)
  4. Buat reproduksi minimal dan laporkan sebagai Issue

Referensi

コメント

タイトルとURLをコピーしました