Hydration mismatch SSR terjadi saat HTML hasil render di server tidak sama dengan output render awal di browser. Akibatnya, UI bisa berkedip, event handler tidak terpasang seperti yang diharapkan, state terasa “lompat”, atau framework menampilkan warning yang sulit ditelusuri. Masalah ini jarang selesai hanya dengan menambah guard di komponen; akar persoalannya biasanya adalah keputusan desain yang tidak mendefinisikan dengan jelas apa yang boleh dirender di server, kapan state dihitung, dan data apa yang harus deterministik.
Karena itu, design doc bukan sekadar dokumen formal. Untuk kasus SSR, design doc adalah alat untuk memaksa tim menjawab pertanyaan penting sebelum implementasi: sumber kebenaran data ada di mana, bagian mana yang aman dirender di server, nilai apa yang berubah berdasarkan waktu atau lingkungan, dan bagaimana memverifikasi bahwa markup awal tetap konsisten antara server dan client. Artikel ini memakai kerangka berpikir design doc yang efektif: jelaskan masalah, batasan, keputusan, alternatif, risiko, dan rencana verifikasi secara eksplisit.
Mengapa hydration mismatch perlu dibahas di design doc
Banyak tim memperlakukan hydration mismatch sebagai bug implementasi frontend. Padahal, mismatch sering lahir dari keputusan arsitektur yang tidak ditulis sejak awal, misalnya:
- Komponen membaca
window,localStorage, atau ukuran viewport saat render pertama. - Label waktu dihitung terpisah di server dan client.
- Urutan data bergantung pada locale, timezone, atau generator acak.
- Flag fitur, cookie, atau data sesi tersedia di server tetapi berbeda saat client bootstrap.
- Komponen menampilkan fallback berbeda antara SSR dan render client awal.
Design doc yang baik membuat asumsi-asumsi ini terlihat. Tujuannya bukan menghasilkan dokumen panjang, tetapi memastikan keputusan render yang memengaruhi SSR dicatat sebelum kode menyebar ke banyak komponen.
Gejala hydration mismatch yang sering muncul
Sebelum menulis design doc, tim perlu punya bahasa yang sama tentang gejalanya. Beberapa tanda umum:
- Warning seperti Text content does not match server-rendered HTML.
- UI berkedip saat halaman selesai dimuat.
- Nilai teks, atribut, atau class berubah sesaat setelah hydration.
- Komponen interaktif gagal merespons sampai render ulang selesai.
- Bug hanya muncul di production karena SSR aktif penuh, tetapi sulit direproduksi di development.
Gejala ini penting dicantumkan dalam bagian masalah pada design doc karena menentukan strategi mitigasi. Mismatch pada teks waktu tentu berbeda penanganannya dengan mismatch pada struktur DOM atau daftar item.
Sumber utama state non-deterministic pada SSR
1. Data berbasis waktu
Nilai dari Date.now(), new Date(), atau formatter tanggal bisa berbeda antara proses server dan browser. Perbedaan kecil beberapa milidetik saja cukup untuk menghasilkan teks yang berbeda.
Contoh buruk:
export function LastUpdated() {
return <span>{new Date().toLocaleTimeString()}</span>
}Mengapa bermasalah: server merender jam tertentu, tetapi client menghitung ulang saat hydration dan menghasilkan string lain. Jika locale atau timezone berbeda, hasilnya makin tidak stabil.
2. Browser-only API
Akses ke window, document, matchMedia, localStorage, atau ukuran layar tidak tersedia atau tidak bermakna di server. Meski aksesnya dibungkus kondisi, Anda tetap bisa membuat output awal berbeda.
function ThemeLabel() {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches
return <span>{isDark ? 'Dark' : 'Light'}</span>
}Masalahnya bukan hanya error di server. Jika server merender fallback “Light” tetapi client langsung menghitung “Dark”, maka markup awal tidak sama.
3. Perbedaan server-client dalam locale, timezone, dan environment
Formatter angka, tanggal, sorting, bahkan kapitalisasi bisa berbeda jika server dan browser menggunakan locale atau timezone yang tidak identik. Begitu juga ketika environment variable, cookie, atau header request memengaruhi tampilan, tetapi tidak diserialisasi dengan benar ke client.
4. Data acak atau ID yang tidak stabil
Pemakaian Math.random(), generator ID non-deterministic, atau urutan array yang tidak stabil bisa menghasilkan atribut atau konten berbeda. Ini sering terjadi pada daftar, key, atau identifier yang dibentuk saat render.
5. Kondisi render yang bergantung pada state client awal
Contoh klasik: status login, preferensi tema, atau eksperimen A/B dibaca dari storage di client, sementara server hanya punya sebagian informasi. Jika tidak ada kontrak data yang jelas, server dan client memulai dari state berbeda.
Bagaimana design doc membantu mencegah mismatch
Design doc yang efektif untuk fitur SSR tidak perlu panjang, tetapi harus menjawab beberapa hal berikut secara konkret.
Definisikan output SSR yang diharapkan
Tulis apa yang harus sudah benar pada HTML server, dan apa yang boleh menunggu sampai client aktif. Ini memisahkan dua kategori penting:
- Harus deterministik saat SSR: judul, harga, nama produk, status login jika tersedia dari request, struktur layout utama.
- Boleh ditunda setelah hydration: preferensi viewport, animasi, data yang hanya tersedia di browser, widget yang tidak kritis.
Tanpa pemisahan ini, developer cenderung merender semuanya sekaligus dan baru memperbaiki warning setelah muncul.
Catat sumber data dan titik evaluasi state
Untuk setiap bagian UI, tulis:
- dari mana data berasal,
- apakah data tersedia di server,
- apakah data perlu diserialisasi ke client,
- apakah nilai itu stabil antara SSR dan hydration.
Ini memaksa tim membedakan antara server-known state dan client-only state.
Tulis risiko non-determinism secara eksplisit
Jika komponen memakai waktu lokal pengguna, timezone browser, ukuran layar, atau storage, tulis konsekuensinya. Dengan begitu, review design doc bisa fokus pada strategi mitigasi sebelum implementasi dimulai.
Sertakan rencana verifikasi
Banyak design doc berhenti di arsitektur. Untuk SSR, itu belum cukup. Tambahkan cara membuktikan bahwa render awal aman: snapshot HTML server, perbandingan output, pengujian lintas timezone, atau logging saat hydration warning muncul.
Template design doc ringkas untuk fitur frontend SSR
Berikut template singkat yang praktis untuk dipakai pada fitur SSR baru atau refactor komponen yang rawan mismatch.
Judul
- [Nama fitur] SSR rendering contract
Masalah
- UI apa yang dirender via SSR?
- Gejala hydration mismatch apa yang pernah/sudah mungkin muncul?
- Mengapa mismatch ini berisiko bagi UX atau debugging?
Tujuan
- HTML server dan render client awal identik untuk bagian kritis.
- State client-only tidak mengubah markup sebelum hydration selesai.
Non-tujuan
- Tidak memaksakan semua widget menjadi SSR penuh.
- Tidak membahas optimasi performa di luar kebutuhan stabilitas render.
Konteks data
- Data apa yang tersedia saat request di server?
- Data apa yang hanya tersedia di browser?
- Apakah ada timezone/locale/cookie/flag yang memengaruhi output?
Keputusan render
- Bagian A: SSR penuh, deterministik.
- Bagian B: placeholder stabil di SSR, isi final setelah hydration.
- Bagian C: client-only, tidak dirender di server.
Sumber non-deterministic
- Waktu/tanggal
- Random/ID
- Browser API
- Viewport/media query
- Storage lokal
- Session/flag yang tidak terserialisasi
Strategi mitigasi
- Serialisasi data server ke payload client.
- Gunakan placeholder yang sama antara server dan client awal.
- Tunda pembacaan browser-only API ke effect/hook client.
- Normalisasi locale/timezone bila perlu.
Alternatif yang dipertimbangkan
- SSR penuh
- Client-only untuk subkomponen tertentu
- Progressive enhancement dengan fallback statis
Verifikasi
- Cara reproduksi mismatch
- Test SSR vs hydration
- Logging warning hydration
- Uji lintas locale/timezone
Risiko dan trade-off
- Fallback kurang personal pada render awal
- Tambahan kompleksitas serialisasi state
- Potensi delay kecil sebelum data client-only tampilTemplate ini sengaja ringkas. Yang penting bukan banyaknya bagian, melainkan kejelasan kontrak render.
Checklist keputusan render untuk mencegah hydration mismatch SSR
Sebelum implementasi dimulai, gunakan checklist berikut saat review design doc:
- Apakah nilai yang dirender di server akan dihitung ulang di client?
Jika ya, apakah hasilnya pasti sama? - Apakah komponen memakai tanggal, waktu, locale, timezone, atau formatter?
Jika ya, apakah inputnya diserialisasi dan dinormalisasi? - Apakah render bergantung pada browser-only API?
Jika ya, apakah bagian itu ditunda sampai hydration selesai? - Apakah struktur DOM berubah berdasarkan viewport atau media query?
Jika ya, hindari memutuskan struktur utama saat SSR dari data yang tidak tersedia. - Apakah state awal berasal dari cookie, header, session, atau storage?
Pastikan sumber kebenarannya jelas dan tersedia konsisten di kedua sisi. - Apakah daftar item punya urutan dan key yang stabil?
Jangan bentuk key dari random atau index jika urutan bisa berubah. - Apakah fallback SSR sama dengan render client pertama?
Jika berbeda, warning mungkin tetap muncul walau data final benar. - Apakah ada bagian yang sebaiknya client-only?
Tidak semua hal harus dipaksa SSR jika nilai awal memang bergantung penuh pada browser.
Contoh skenario React/Next.js: waktu relatif dan preferensi tema
Skenario 1: label waktu relatif
Misalkan halaman artikel SSR ingin menampilkan teks “diperbarui 5 menit lalu”. Secara UX ini menarik, tetapi rawan mismatch jika dihitung langsung saat render di server dan client.
Contoh rawan mismatch:
function UpdatedAt({ updatedAt }) {
const minutes = Math.floor((Date.now() - new Date(updatedAt).getTime()) / 60000)
return <span>Diperbarui {minutes} menit lalu</span>
}Masalahnya: Date.now() pada server dan client hampir pasti berbeda.
Pendekatan yang lebih aman:
- SSR menampilkan timestamp absolut yang stabil, misalnya format ISO yang sudah diproses di server.
- Setelah hydration, client boleh meningkatkan tampilan menjadi waktu relatif.
function UpdatedAt({ initialLabel, updatedAt }) {
const [label, setLabel] = React.useState(initialLabel)
React.useEffect(() => {
const minutes = Math.floor((Date.now() - new Date(updatedAt).getTime()) / 60000)
setLabel(`Diperbarui ${minutes} menit lalu`)
}, [updatedAt])
return <span>{label}</span>
}Mengapa lebih aman: render awal client memakai initialLabel yang sama dengan SSR. Perubahan terjadi setelah hydration, jadi bukan mismatch.
Skenario 2: tema dari localStorage
Kasus lain adalah tema gelap/terang. Jika komponen langsung membaca localStorage saat render, SSR tidak punya informasi yang sama.
Lebih baik:
- Jika tema tersedia dari cookie request, gunakan itu sebagai sumber kebenaran SSR.
- Jika tidak, render fallback yang stabil dan sinkronkan preferensi setelah client aktif.
Dalam design doc, keputusan pentingnya bukan detail hook, melainkan kontrak: apakah tema termasuk bagian kritis yang harus benar sejak SSR, atau boleh diperbarui setelah hydration.
Contoh skenario Nuxt: viewport-dependent layout
Pada Nuxt atau aplikasi Vue SSR lain, mismatch sering muncul ketika layout berbeda total antara mobile dan desktop berdasarkan lebar layar browser. Server tidak tahu viewport aktual pengguna kecuali memakai sinyal tambahan yang belum tentu akurat.
Anti-pattern:
- SSR langsung memilih DOM mobile atau desktop berdasarkan
window.innerWidth. - Menentukan jumlah item carousel saat render pertama dari ukuran layar browser.
Pendekatan yang lebih aman:
- Biarkan struktur HTML utama tetap sama di SSR.
- Gunakan CSS responsif untuk perbedaan presentasi bila memungkinkan.
- Jika logika benar-benar harus bergantung pada viewport, tunda peningkatan interaktif sampai client tersedia.
Alasan pendekatan ini bekerja: CSS bisa mengubah tampilan tanpa mengubah markup hasil SSR, sehingga risiko hydration mismatch jauh lebih kecil dibanding memecah struktur DOM berdasarkan data yang hanya ada di browser.
Strategi verifikasi yang sebaiknya ditulis di design doc
1. Verifikasi kontrak SSR vs render client awal
Pastikan tim punya cara mengecek bahwa output awal konsisten. Ini bisa berupa:
- pengujian integrasi yang merender halaman SSR lalu memantau warning hydration,
- snapshot HTML untuk komponen penting,
- review manual pada kasus yang melibatkan locale, timezone, dan cookie.
Tujuannya bukan membuktikan semua state akhir sama, melainkan bahwa markup awal sebelum interaksi tidak menyimpang.
2. Uji skenario lintas environment
Hydration mismatch sering tersembunyi jika semua developer memakai timezone dan browser yang sama. Tambahkan skenario uji seperti:
- timezone server berbeda dari timezone client,
- locale browser berbeda dari default server,
- storage lokal berisi preferensi yang tidak ada di request server,
- halaman dibuka dengan cache atau cookie lama.
3. Logging yang fokus pada konteks mismatch
Jika framework atau environment memungkinkan, catat informasi berikut saat warning terjadi:
- route dan komponen yang terlibat,
- props SSR yang diserialisasi,
- nilai client awal yang menyebabkan divergence,
- locale, timezone, dan flag penting.
Debugging hydration lebih cepat jika Anda tidak hanya tahu bahwa mismatch terjadi, tetapi juga tahu input apa yang membuat server dan client berbeda.
4. Isolasi komponen yang dicurigai
Saat mismatch sulit dicari, bungkus atau pecah halaman menjadi unit yang lebih kecil dan verifikasi satu per satu. Dalam banyak kasus, satu komponen kecil yang membaca waktu atau storage bisa menyebabkan warning pada subtree besar.
Anti-pattern yang sering memicu UI membingungkan setelah hydration
- Menghitung nilai dinamis langsung di JSX/template saat render pertama.
Contoh: waktu relatif, angka acak, atau hasil formatter berbasis locale yang tidak dikendalikan. - Membaca browser-only API untuk menentukan struktur DOM.
Misalnya memilih layout mobile/desktop dengan percabangan render di komponen SSR. - Mengandalkan fallback berbeda antara server dan client.
Server menampilkan placeholder A, client langsung merender placeholder B sebelum effect jalan. - Mencampur sumber kebenaran state.
Sebagian status berasal dari cookie server, sebagian lagi dari storage client, tanpa aturan prioritas yang ditulis. - Menggunakan key yang tidak stabil.
Jika urutan list berubah atau key dibentuk dari nilai acak, hydration bisa gagal memasangkan node dengan benar. - Memaksa semua komponen menjadi SSR meskipun datanya client-only.
Hasilnya justru lebih rapuh dibanding menjadikannya client-only atau progressive enhancement.
Trade-off yang perlu dijelaskan sejak awal
Mencegah hydration mismatch hampir selalu berarti memilih salah satu kompromi berikut:
- Stabilitas vs personalisasi awal.
Menunda pembacaan preferensi browser membuat render awal lebih stabil, tetapi mungkin kurang personal untuk sesaat. - SSR penuh vs client-only parsial.
Beberapa widget lebih aman dirender hanya di client. Namun ini bisa mengurangi manfaat SEO atau perceived performance pada bagian tertentu. - Serialisasi data lebih lengkap vs kompleksitas implementasi.
Mengirim state server ke client membantu menjaga konsistensi, tetapi menambah kontrak data yang harus dirawat.
Design doc yang baik tidak menyembunyikan trade-off ini. Justru trade-off itulah yang membantu reviewer menilai apakah keputusan render masuk akal.
Struktur design doc yang efektif untuk debugging, bukan sekadar dokumentasi
Supaya design doc benar-benar membantu saat bug muncul, tulis dengan orientasi investigasi:
- Masalah konkret: sebutkan UI mana yang berisiko mismatch.
- Asumsi eksplisit: locale, timezone, cookie, session, viewport, dan storage apa yang diasumsikan tersedia.
- Kontrak render: bagian mana harus identik saat SSR dan hydration awal.
- Mitigasi: placeholder stabil, serialisasi state, atau client-only boundary.
- Verifikasi: bagaimana mismatch akan terdeteksi sebelum masuk production.
Dengan format seperti ini, design doc menjadi alat kerja lintas peran: engineer frontend memahami batas render, backend tahu data request apa yang harus tersedia, QA tahu skenario uji penting, dan reviewer bisa menilai risiko mismatch sebelum implementasi membesar.
Penutup
Menulis design doc untuk debugging hydration mismatch SSR pada dasarnya adalah menulis kontrak antara server render dan client render. Jika kontraknya kabur, warning hydration hanya gejala dari keputusan yang tidak pernah dijelaskan. Jika kontraknya jelas, debugging menjadi jauh lebih cepat karena tim tahu apa yang seharusnya identik, apa yang boleh berubah setelah hydration, dan input apa yang perlu diverifikasi.
Untuk fitur SSR baru, mulailah dari dokumen ringkas: definisikan data deterministik, tandai sumber state non-deterministic, putuskan boundary client-only, lalu tulis strategi verifikasi. Langkah sederhana ini biasanya lebih efektif daripada memburu warning satu per satu setelah UI di production terlihat membingungkan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!