Astro 5 Hydration Mismatch: Penyebab dan Cara Fixnya [2026]
Sering liat warning hydration mismatch di Astro 5 islands? Nih gue jelasin kenapa SSR dan markup client beda, plus cara fix yang beneran ampuh di production.
![Astro 5 Hydration Mismatch: Penyebab dan Cara Fixnya [2026]](https://cdn.asepalazhari.com/images/articles/development/fixing-astro-hydration-mismatch-errors.jpeg)
Warning di Console yang Bikin Gue Mikir Ulang
Bulan lalu gue lagi ship dashboard kecil yang dibangun pake Astro islands. Di browser keliatannya semua oke. Terus gue buka console, eh ada tumpukan warning hydration mismatch, yang ngeluh soal text content yang ga match antara server sama client. Halamannya tetep jalan sih. Ga ada yang crash. Tapi warning itu terus muncul tiap kali reload, dan itu ngeganggu banget.
Gue pikir cuma kebetulan. Gue refresh. Muncul lagi. Gue coba halaman lain. Warning yang sama, komponen yang beda. Di titik itu gue sadar ini bukan kebetulan. Ada sesuatu di island gue yang render satu hal di server dan hal lain di browser, dan Astro lagi ngasih tau gue lewat satu-satunya cara yang dia bisa.
Kalo lo pernah liat warning kayak “Hydration completed but contains mismatches” atau “Text content does not match server rendered HTML” di project Astro lo, lo ga sendirian kok. Ini salah satu masalah paling umum yang bakal ketemu developer begitu mereka mulai gabungin halaman server rendered sama island yang interaktif. Yuk kita bahas kenapa ini bisa terjadi dan gimana cara fixnya sampe tuntas.
Apa Sih Sebenernya Hydration Mismatch di Astro 5
Server Rendering vs Client Hydration
Astro render halaman lo di server duluan. Dia bikin HTML, kirim ke browser, dan halamannya langsung muncul. Itu inti dari islands architecture, dan ini salah satu alasan kenapa Astro jadi framework terbaik buat blog performa tinggi. Sebagian besar halaman lo tetep berupa HTML statis tanpa JavaScript sama sekali.
Terus, buat bagian yang lo tandain interaktif, browser bakal download kode komponennya dan nge-hydrate. Hydration artinya framework ngambil HTML hasil server rendering yang udah ada, terus nempelin event listener dan state ke situ, bukan ngebuang dan render dari nol.
Biar hydration jalan mulus, markup yang di-render client di pass pertama harus sama persis sama markup yang udah dikirim server. Kalo dua-duanya beda, framework harus benerin DOM-nya secara langsung. Proses benerin itulah yang bikin warning yang lo liat muncul, dan dalam beberapa kasus bisa bikin flicker, focus ilang, atau interaktivitas rusak.
Gimana Client Directive Ngubah Cerita
Astro ngasih lo beberapa client directive buat ngatur kapan dan gimana komponen di-hydrate:
client:loadhydrate begitu halaman selesai loadclient:idlenunggu sampe browser idleclient:visiblenunggu sampe komponennya keliatan pas di-scrollclient:onlyskip server rendering sama sekali dan cuma render di browser
Masing-masing ngubah timing hydration, tapi ga ada satu pun yang ngubah syarat bahwa render client pertama harus sama dengan hasil server, kecuali client:only, yang ngehindarin masalah ini dengan cara ga pernah render di server sama sekali. Milih client directive yang salah buat komponen yang bergantung sama data browser-only itu salah satu cara paling cepet buat berakhir kena mismatch.
Baca Juga: Optimasi Astro: Background Caching dan CDN Gambar Lokal
Tersangka Utama di Balik Hydration Mismatch
Pas gue mulai gali lebih dalem, gue nemuin hampir semua warning hydration ujung-ujungnya balik ke salah satu dari empat penyebab ini.
Nilai Waktu dan Random
Apapun yang ngehasilin nilai beda tiap kali jalan itu bahaya buat komponen server rendered. Date.now(), new Date(), dan Math.random() bakal ngembaliin satu nilai pas server render halamannya, dan nilai lain pas browser nge-hydrate.
// Ini bakal mismatch hampir tiap saat
export default function Timestamp() {
return <span>Rendered at {Date.now()}</span>;
}Server nyetempel HTML-nya dengan satu angka. Client nyetempel render pertamanya dengan angka lain. Astro bandingin keduanya, nemu bedanya, dan nyatet warning-nya.
Global yang Cuma Ada di Browser
window, document, localStorage, dan navigator ga ada di server. Kalo komponen lo baca dari salah satu itu pas initial render, server ga punya apa-apa buat dipake dan jatuh ke default, sementara client langsung punya nilai aslinya.
// window undefined di server
export default function ThemeBadge() {
const theme = window.localStorage.getItem("theme") ?? "light";
return <span className={`badge badge-${theme}`}>{theme}</span>;
}Server render badge-light. Client, yang punya akses ke localStorage, mungkin render badge-dark. Beda itulah yang bikin warning-nya muncul.
Drift Locale dan Timezone
Yang ini agak licik. Kalo server lo jalan di UTC dan visitor lo ada di timezone yang beda, format tanggal pake toLocaleString atau toLocaleDateString bisa ngehasilin dua string berbeda buat timestamp yang sama persis.
// Date object sama, output beda tergantung runtime
<span>{new Date(post.publishDate).toLocaleDateString()}</span>Gue pernah ngalamin masalah ini persis di blog ini. Satu artikel yang publish jam 09:00 waktu Jakarta nunjukin tanggal yang beda di halaman server rendered dibanding setelah hydration buat visitor di timezone lain.
Conditional Rendering Berdasarkan State Browser
Apapun yang nge-branch berdasarkan ukuran layar, kecepatan koneksi, atau feature detection bakal render beda tergantung dia jalan di server atau di browser.
// matchMedia ga ada pas server rendering
const isMobile = typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches;
return isMobile ? <MobileNav /> : <DesktopNav />;Di server, typeof window hasilnya "undefined", jadi selalu render DesktopNav. Begitu hydrate di viewport kecil, dia langsung ganti ke MobileNav, dan warning mismatch-nya muncul sebelum pergantiannya selesai.
Cara Diagnosa dengan Cepat
Sebelum lompat ke fix, persempit dulu komponen mana yang jadi biang masalahnya. Beberapa kebiasaan ini lumayan ngehemat waktu nebak-nebak buat gue.
- Buka console browser dan baca warning-nya pelan-pelan. Framework modern biasanya nge-print tag atau text node persis yang ga match.
- Liat page source, bukan rendered DOM, dan bandingin sebelahan sama tampilan setelah halamannya kelar load. Bedanya bakal langsung keliatan begitu lo taro berdampingan.
- Comment out island satu-satu, mulai dari yang nyentuh tanggal, nilai random, atau
window. Kalo warning-nya ilang, lo nemu tersangkanya. - Cari di komponen lo kata
Date,Math.random,window,document,localStorage, danmatchMedia. Dari pengalaman gue, lima kata ini nutupin sebagian besar bug hydration.
Cara Fix Hydration Mismatch Step by Step
Fix 1: Tunda Logic Browser Only Sampe Setelah Mount
Fix paling reliable adalah render default yang aman di pass pertama, terus update komponennya begitu udah mounted di browser. Di React, artinya baca nilai browser only di dalam useEffect dan simpen di state.
import { useEffect, useState } from "react";
export default function ThemeBadge() {
const [theme, setTheme] = useState("light");
useEffect(() => {
const stored = window.localStorage.getItem("theme");
if (stored) setTheme(stored);
}, []);
return <span className={`badge badge-${theme}`}>{theme}</span>;
}Server dan render client pertama dua-duanya ngehasilin badge-light. Hydration berhasil mulus. Terus, begitu komponennya mount, useEffect jalan, nilai aslinya ke-load, dan komponennya ke-update. User bakal liat tema yang bener dalam satu dua frame, dan ga ada warning di console.
Fix 2: Pilih Client Directive yang Tepat
Kalo komponennya emang ga bisa di-render dengan cara yang sama di server dan client, jangan dipaksain. Pake client:only dan kasih tau Astro buat skip server rendering buat island itu sepenuhnya.
---
import ThemeBadge from "../components/ThemeBadge";
---
<ThemeBadge client:only="react" />Ini pilihan yang tepat buat komponen yang bener-bener nempel sama state browser, kayak yang baca dari localStorage, geolocation, atau matchMedia pas first render. Lo kehilangan first paint instan buat komponen itu, tapi lo ngilangin mismatch-nya sepenuhnya. Buat widget kecil yang ga kritis, trade off ini biasanya worth it. Kalo lo lagi nyusun widget interaktif di atas Astro, pola komponen di panduan integrasi Astro dan shadcn/ui gue cocok banget dipadu sama pendekatan ini.
Baca Juga: Cara Fix ERR_UPLOAD_FILE_CHANGED di Next.js Production
Fix 3: Render Placeholder yang Stabil di Server
Buat komponen yang bergantung sama nilai kayak lebar layar, render placeholder netral pas server rendering dan ganti setelah komponennya mount.
import { useEffect, useState } from "react";
export default function ResponsiveNav() {
const [mounted, setMounted] = useState(false);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
setMounted(true);
const mql = window.matchMedia("(max-width: 768px)");
setIsMobile(mql.matches);
const handler = (event: MediaQueryListEvent) => setIsMobile(event.matches);
mql.addEventListener("change", handler);
return () => mql.removeEventListener("change", handler);
}, []);
if (!mounted) return <DesktopNav />;
return isMobile ? <MobileNav /> : <DesktopNav />;
}Server dan render client pertama dua-duanya nunjukin DesktopNav. Hydration-nya match. Begitu mount, komponennya cek viewport asli dan ganti kalo emang perlu. Kebanyakan user bahkan ga bakal sadar ada pergantian, dan console lo tetep bersih.
Fix 4: Pindahin Nilai yang Sensitif Waktu ke Client
Buat timestamp dan tanggal relatif, format setelah komponennya mount, bukan pas initial render. Lo juga bisa normalisasi timezone di server biar dua-duanya sepakat.
import { useEffect, useState } from "react";
export default function PublishedAt({ iso }: { iso: string }) {
const [label, setLabel] = useState(() => new Date(iso).toISOString().slice(0, 10));
useEffect(() => {
setLabel(new Date(iso).toLocaleDateString());
}, [iso]);
return <time dateTime={iso}>{label}</time>;
}Render pertama selalu pake string tanggal ISO, yang sama persis di server dan client. Begitu mounted, komponennya ganti ke versi locale formatted yang cocok sama browser visitor-nya. Ga ada mismatch, dan tanggalnya tetep keliatan natural buat tiap pembaca.
Testing Fix Lo
Setelah lo apply fix-nya, pastiin emang beneran ampuh.
- Jalanin production build dan preview lokal pake
astro builddanastro preview. Mode development kadang lebih toleran dibanding production. - Buka halamannya dengan console browser kebuka dan reload beberapa kali. Warning-nya harus ilang di tiap load, bukan cuma kebanyakan.
- Throttle network di devtools dan reload lagi. Koneksi yang lebih lambat bikin jarak antara server render dan hydration makin lebar, dan itu pas banget waktu mismatch suka muncul.
- Test pake locale dan timezone sistem yang beda kalo komponen lo nyentuh tanggal. Ganti locale OS lo selama lima menit aja bisa ngebongkar bug yang biasanya cuma muncul buat visitor di luar sana.
Pertanyaan yang Sering Ditanyain
Apa warning hydration mismatch artinya situs gue rusak? Ga selalu. Halamannya biasanya tetep jalan karena framework benerin DOM-nya setelah ketemu mismatch. Tapi proses benerin itu makan waktu, bisa bikin flicker yang keliatan, dan kadang ngilangin event listener atau focus state. Anggep warning ini sebagai sinyal buat dibenerin, bukan sesuatu yang aman buat dicuekin terus-terusan.
Kenapa ini cuma muncul di production dan ga di development? Build development sering jalan dengan extra check dan jalur rendering yang lebih lambat dan toleran. Build production lebih ramping dan ketat soal timing, jadi perbedaan kecil antara output server dan client lebih gampang muncul jadi mismatch beneran begitu situs lo udah live.
Boleh ga gue cuma diemin warning-nya daripada benerin? Beberapa framework punya semacam jalan pintas mirip suppressHydrationWarning yang nyembunyiin pesannya buat satu elemen doang. Itu cuma nyembunyiin gejalanya. Server dan client tetep render konten yang beda, yang artinya user lo tetep liat kilatan data yang salah sebelum pergantiannya kejadian. Mendingan benerin sumbernya daripada nge-mute sinyalnya.
Apa pindah ke client:only ngerusak performance? Itu ngilangin versi server rendered dari island itu, jadi visitor bakal liat loading state sebentar, bukan konten instan, buat komponen itu. Buat widget interaktif yang kecil, trade off ini biasanya ga keliatan. Buat section besar yang isinya banyak, mendingan pake pendekatan placeholder dari Fix 3 biar lo tetep dapet first paint yang cepet.
Apa masalah ini cuma khusus buat React island di Astro? Ga juga. Penyebab dasarnya sama buat island Vue, Svelte, Solid, dan Preact, dan buat framework apapun yang nge-hydrate HTML server rendered. Fix di panduan ini, nunda logic browser only, milih strategi rendering yang tepat, dan jaga render pertama tetep deterministic, berlaku buat semuanya.
Penutup
Warning hydration mismatch keliatan serem pas pertama kali lo liat tumpukan teks merah di console, tapi penyebab dasarnya hampir selalu sederhana. Ada sesuatu di komponen lo yang ngehasilin hasil beda di server dibanding di browser, biasanya gara-gara waktu, nilai random, global yang cuma ada di browser, atau conditional rendering berdasarkan state client.
Begitu lo tau apa yang harus dicari, benerinnya tinggal soal bikin render pertama deterministic dan nunda apapun yang khusus browser sampe setelah komponennya mount. Terapin pola ini secara konsisten di semua island lo, dan warning itu bakal ilang dari console lo buat selamanya.
Kalo lo masih ngejar mismatch yang bandel setelah nyoba semua fix ini, isolasi komponennya, kecilin sampe versi paling minimal yang masih nunjukin warning-nya, terus bangun ulang dikit-dikit. Sembilan dari sepuluh kali, biang keroknya ternyata satu baris yang ga bakal pernah lo curigain.

![Cara Fix ERR_UPLOAD_FILE_CHANGED di Next.js Production [2026]](https://cdn.asepalazhari.com/images/articles/development/fixing-err-upload-file-changed-nextjs-production.png)
