SSR hemat resource berarti HTML awal harus stabil, murah dirender, dan tidak memaksa browser melakukan koreksi besar saat JavaScript mengambil alih. Jika markup dari server berbeda dengan hasil render pertama di client, framework akan memunculkan hydration mismatch; akibatnya UI dapat berubah mendadak, event handler bisa terpasang ulang, dan perangkat lemah harus membayar biaya CPU tambahan untuk memperbaiki DOM.
Dalam konteks Low Resource Computing yang makin relevan pada 2026, efisiensi render bukan sekadar optimasi kosmetik. Pada ponsel lama, laptop hemat daya, koneksi lambat, atau browser dengan throttling agresif, mismatch kecil bisa terasa sebagai flicker, layout bergeser, atau input yang terasa lambat. Referensi seperti komunitas Low Resource Computing di Dartmouth menunjukkan arah yang sama: sistem yang baik harus bekerja konsisten di lingkungan dengan sumber daya terbatas. Untuk SSR, kuncinya adalah determinisme render: hasil render server dan render awal client harus sama.
Mengapa hydration mismatch lebih mahal di perangkat low resource
Pada perangkat kuat, mismatch kadang hanya tampak sebagai warning di console. Pada perangkat lemah, dampaknya lebih nyata karena browser harus:
- membandingkan DOM server dengan virtual tree di client,
- membuang bagian yang tidak cocok lalu merender ulang,
- menjalankan JavaScript tambahan untuk state koreksi,
- memicu style recalculation dan layout ulang.
Itulah sebabnya SSR tidak otomatis hemat resource. SSR baru efisien jika HTML awal dapat dihydrate tanpa koreksi besar. Jika tidak, kita hanya memindahkan biaya render dari server ke client dengan cara yang lebih rumit.
Penyebab utama hydration mismatch
1. Nilai non-deterministik saat render
Penyebab paling umum adalah menghasilkan nilai yang berbeda antara server dan client pada render pertama. Contohnya:
Date.now()new Date()untuk teks waktu saat renderMath.random()- ID acak yang dibuat langsung di template atau komponen
Masalahnya sederhana: server merender satu nilai, client menghitung nilai lain, lalu hasilnya tidak sama.
Anti-pattern:
// Render awal di server dan client akan berbeda
export default function PromoBanner() {
return <p>Kode sesi: {Math.random().toString(36).slice(2, 8)}</p>;
}Perbaikan: buat nilai di server lalu kirim sebagai props, atau tampilkan placeholder stabil hingga client siap.
// Nilai dibuat sekali di server / data loader
export default function PromoBanner({ sessionCode }) {
return <p>Kode sesi: {sessionCode}</p>;
}Untuk waktu relatif seperti “baru saja” atau “5 menit lalu”, jangan hitung saat SSR kecuali Anda mengendalikan inputnya dengan ketat. Lebih aman kirim timestamp mentah, render format netral, lalu tingkatkan di client setelah hydration.
2. Akses window, document, atau localStorage terlalu dini
Server tidak memiliki window, document, atau localStorage. Bahkan jika akses tersebut dijaga agar tidak crash, hasil UI tetap bisa berbeda bila state awal bergantung pada data browser.
Anti-pattern:
function ThemeLabel() {
const theme = localStorage.getItem('theme') || 'light';
return <span>Tema: {theme}</span>;
}Di server, nilai ini tidak tersedia. Di client, nilainya mungkin dark. Hasilnya: server merender “light”, client langsung mengubah ke “dark”.
Perbaikan:
- Jika data penting untuk render awal, baca dari cookie atau header yang juga tersedia di server.
- Jika data hanya kosmetik, render fallback stabil terlebih dulu, lalu update setelah komponen terpasang di client.
function ThemeLabel({ initialTheme = 'light' }) {
const [theme, setTheme] = useState(initialTheme);
useEffect(() => {
const stored = window.localStorage.getItem('theme');
if (stored && stored !== theme) setTheme(stored);
}, [theme]);
return <span>Tema: {theme}</span>;
}Pendekatan ini bekerja jika initialTheme dari server sudah masuk akal, misalnya berasal dari cookie. Jika tidak, siapkan UI netral agar perubahan setelah mount tidak membingungkan.
3. Perbedaan timezone dan locale
Format tanggal, angka, mata uang, bahkan urutan hari dapat berbeda antara lingkungan server dan browser. Ini sering luput karena aplikasi terlihat benar di mesin developer, lalu bermasalah pada deployment dengan timezone server berbeda.
Contoh masalah:
- server merender
01/02/2026, client menganggap format lain, - server memakai timezone UTC, client menampilkan waktu lokal,
toLocaleString()menghasilkan format berbeda.
Praktik aman:
- Untuk SSR, render format yang stabil, misalnya ISO singkat atau timestamp yang sudah dinormalisasi.
- Jika butuh locale pengguna, kirim locale eksplisit dari request, bukan menebak di client saja.
- Untuk elemen waktu yang sangat kontekstual, pertimbangkan client-only enhancement.
// Lebih stabil: server merender data mentah yang konsisten
<time dateTime={publishedAtIso}>{publishedAtIso}</time>
// Setelah hydration, boleh ditingkatkan menjadi format lokal pengguna4. State awal async yang tidak sinkron
Mismatch juga muncul ketika server merender dengan data A, tetapi client memulai render pertama dengan state kosong atau data B sebelum hasil fetch selesai. Ini umum terjadi pada pencarian, notifikasi, keranjang belanja, atau profil pengguna.
Anti-pattern:
function CartSummary() {
const [items, setItems] = useState([]);
useEffect(() => {
fetch('/api/cart').then(r => r.json()).then(setItems);
}, []);
return <span>{items.length} item</span>;
}Jika server sebelumnya merender jumlah item dari sesi pengguna, tetapi client selalu mulai dari array kosong, UI awal akan berubah saat hydration.
Perbaikan: gunakan data SSR yang sama sebagai state awal client.
function CartSummary({ initialItems }) {
const [items, setItems] = useState(initialItems);
useEffect(() => {
fetch('/api/cart')
.then(r => r.json())
.then(nextItems => {
// update hanya jika benar-benar berubah
setItems(nextItems);
});
}, []);
return <span>{items.length} item</span>;
}Prinsipnya: data yang dipakai SSR harus menjadi sumber state awal yang sama di client. Revalidasi boleh dilakukan kemudian, tetapi render pertama tidak boleh memulai dari kondisi lain tanpa alasan kuat.
5. Conditional rendering berbasis ukuran layar
Mendeteksi ukuran layar saat render adalah sumber mismatch klasik. Server tidak tahu viewport aktual browser. Jika Anda merender komponen berbeda untuk mobile dan desktop berdasarkan window.innerWidth, hasil SSR hampir pasti tidak stabil.
Anti-pattern:
function Navigation() {
const mobile = window.innerWidth < 768;
return mobile ? <MobileMenu /> : <DesktopMenu />;
}Perbaikan terbaik: gunakan CSS untuk responsivitas layout jika kontennya semantik sama. Biarkan SSR menghasilkan markup yang stabil, lalu media query yang menentukan tampilan.
function Navigation() {
return (
<nav>
<button className="menu-toggle">Menu</button>
<ul className="menu-list">
<li>Beranda</li>
<li>Dokumentasi</li>
<li>Profil</li>
</ul>
</nav>
);
}Jika versi mobile dan desktop benar-benar berbeda secara perilaku, render shell netral di SSR lalu pilih varian setelah mount. Ini lebih aman daripada memaksa server menebak viewport.
Pola implementasi yang aman untuk SSR hemat resource
Utamakan render awal yang deterministik
Tanyakan pada setiap komponen SSR: “Apakah output HTML ini bisa dihitung sama persis di server dan client tanpa bergantung pada lingkungan lokal?” Jika jawabannya tidak, pertimbangkan salah satu dari tiga opsi:
- Pindahkan data ke request/server props, misalnya tema dari cookie, locale dari header, atau state sesi dari backend.
- Gunakan placeholder stabil saat SSR, lalu tingkatkan setelah mount.
- Jadikan komponen client-only jika nilainya memang tidak relevan untuk HTML awal.
Gunakan progressive enhancement, bukan koreksi agresif
Untuk perangkat low resource, strategi yang baik adalah menampilkan HTML yang sudah berguna tanpa JavaScript penuh, lalu menambahkan kemampuan secara bertahap. Contohnya:
- SSR menampilkan daftar produk sederhana.
- Setelah hydration, client menambahkan filter interaktif, sort, atau preferensi tampilan tersimpan.
- Elemen non-kritis seperti “waktu relatif”, avatar dinamis, atau panel personalisasi bisa datang belakangan.
Dengan begitu, mismatch berkurang karena SSR fokus pada konten inti yang stabil.
Isolasi bagian yang memang harus client-only
Tidak semua UI layak di-SSR. Beberapa bagian justru lebih murah dan lebih aman jika hanya dirender di client, terutama bila bergantung pada:
- API browser,
- ukuran viewport aktual,
- preferensi lokal pengguna,
- animasi atau visualisasi berat,
- data yang cepat berubah dan tidak kritis untuk tampilan awal.
Trade-off-nya jelas: SEO dan kecepatan konten awal untuk bagian tersebut bisa menurun. Karena itu, client-only sebaiknya dipakai selektif, bukan sebagai jalan pintas untuk semua mismatch.
Kapan menunda hydration dan kapan memindahkan logika ke client-only
Tunda hydration jika HTML awal sudah berguna
Menunda hydration masuk akal bila:
- konten awal sudah dapat dibaca tanpa interaksi,
- interaksi tidak perlu tersedia seketika,
- Anda ingin mengurangi kontensi CPU saat halaman baru dibuka, terutama di perangkat lemah.
Contohnya: komentar, widget rekomendasi, tab sekunder, panel statistik, atau komponen di bawah fold. Menunda hydration mengurangi beban awal, tetapi hanya aman jika markup server tetap final dan tidak menipu pengguna.
Pindahkan ke client-only jika output awal memang tidak bisa dibuat stabil
Pilih client-only jika komponen:
- secara inheren bergantung pada
window, ukuran layar, geolokasi, sensor, atau storage lokal, - tidak penting untuk SEO atau makna utama halaman,
- sering menyebabkan mismatch walau sudah diberi placeholder.
Contoh realistis: pemilih layout dashboard berdasarkan preferensi lokal, panel eksperimen UI, atau widget yang sangat personal.
Aturan praktis: jika suatu komponen harus mengetahui kondisi browser spesifik sebelum bisa menghasilkan markup yang benar, jangan paksa SSR. Beri fallback statis atau jadikan client-only.
Checklist diagnosis hydration mismatch
Gunakan daftar ini saat warning hydration muncul atau UI terasa “melompat” di perangkat lambat:
- Bandingkan HTML server dan render awal client. Cari teks, atribut, urutan node, atau jumlah elemen yang berubah.
- Audit nilai non-deterministik. Periksa penggunaan waktu saat ini, angka acak, ID dinamis, dan format locale.
- Cek akses API browser saat render. Cari
window,document,matchMedia,localStorage, dan sejenisnya. - Validasi state awal. Pastikan data SSR benar-benar dipakai sebagai initial state di client.
- Periksa conditional rendering berbasis viewport. Jika ada cabang mobile/desktop saat render, ubah ke CSS atau shell netral.
- Audit format tanggal/angka. Pastikan locale dan timezone tidak berubah diam-diam antara server dan browser.
- Uji di CPU lambat dan jaringan lambat. Banyak mismatch tampak kecil di desktop cepat, tetapi menjadi jelas saat hydration tertunda.
- Lihat warning framework secara lengkap. Sering kali warning menyebut node atau subtree yang bermasalah.
Contoh perbaikan arsitektural yang sering efektif
Pisahkan data inti dan personalisasi lokal
Render konten inti dari server, lalu tambahkan lapisan personalisasi di client. Misalnya pada halaman artikel:
- judul, isi, penulis, dan tanggal publikasi dari SSR,
- ukuran font pilihan pengguna dari client setelah mount,
- bookmark lokal atau riwayat baca dari client-only.
Pemisahan ini mencegah seluruh halaman menjadi tidak stabil hanya karena preferensi lokal.
Gunakan fallback netral yang tidak membingungkan
Fallback terbaik bukan yang “paling canggih”, tetapi yang paling konsisten. Contoh:
- gunakan label “Memuat preferensi…” alih-alih menebak tema atau bahasa,
- gunakan ikon generik alih-alih avatar personal jika URL avatar belum pasti,
- gunakan struktur tabel/list yang sama, lalu isi detail tambahan setelah data lengkap.
Di perangkat low resource, fallback netral sering lebih baik daripada UI yang berubah dua atau tiga kali.
Minimalkan subtree yang harus re-render
Jika bagian tertentu memang harus berubah setelah mount, batasi area perubahan. Jangan biarkan satu nilai personalisasi menyebabkan rerender pada seluruh layout. Semakin kecil subtree yang berubah, semakin kecil biaya koreksi dan risiko UI bergeser.
Kesalahan umum yang sering dianggap sepele
- Menyimpan logika presentasi penting di
useEffecttanpa fallback SSR yang jelas. Hasilnya bukan mismatch eksplisit, tetapi UI kosong atau meloncat. - Mencampur sumber kebenaran. Server pakai cookie, client pakai localStorage, keduanya tidak sinkron.
- Mengandalkan locale default lingkungan. Di mesin lokal semuanya cocok, di server produksi hasil berbeda.
- Menggunakan SSR untuk widget yang sangat dinamis tapi tidak penting. Biaya hydration lebih besar daripada manfaat HTML awal.
- Merender cabang DOM berbeda hanya karena media query JavaScript. Untuk layout responsif, CSS hampir selalu lebih aman.
Debugging praktis di lingkungan nyata
Log input render, bukan hanya output
Saat mismatch terjadi, log nilai yang memengaruhi render pertama: locale, timezone, cookie, initial state, flag eksperimen, dan hasil pembacaan storage. Fokus pada input sering lebih cepat daripada membaca DOM hasil akhir.
Uji dengan mode “perangkat lemah”
Gunakan throttling CPU dan network pada browser devtools. Tujuannya bukan mencari angka benchmark, tetapi mengamati apakah pengguna sempat melihat SSR sebelum hydration selesai, lalu apakah UI berubah. Pada perangkat lambat, gejala seperti flicker dan teks berganti biasanya lebih mudah terlihat.
Buat komponen “SSR-safe by design”
Tim yang sering membangun SSR sebaiknya punya pedoman sederhana:
- jangan panggil API browser di render path,
- hindari nilai non-deterministik di template,
- pastikan state awal berasal dari payload SSR yang sama,
- gunakan CSS untuk responsivitas sebelum memilih JavaScript,
- pisahkan enhancement interaktif dari konten inti.
Penutup
SSR hemat resource tidak cukup dengan mengirim HTML dari server. Agar benar-benar ringan di perangkat low resource, render awal harus stabil dan dapat dihydrate tanpa koreksi besar. Sebagian besar hydration mismatch berasal dari lima sumber yang bisa diprediksi: nilai non-deterministik, akses API browser terlalu dini, perbedaan timezone/locale, state async yang tidak sinkron, dan conditional rendering berbasis ukuran layar.
Jika Anda memprioritaskan determinisme render, progressive enhancement, dan pemisahan tegas antara konten inti dengan logika khusus browser, aplikasi SSR akan terasa lebih konsisten, lebih hemat CPU, dan jauh lebih tidak membingungkan bagi pengguna perangkat lemah.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!