Render drift atau mismatch antara output server dan client adalah penyebab umum UI tersendat di Next.js App Router. Dalam dua paragraf pertama berikut dijelaskan langsung penyebab utama dan pendekatan yang akan dibahas: perbedaan state antar sisi dan variasi data asinkron yang mengubah markup saat hydration.
Kita akan melalui prosedur pemeriksaan server vs client, memanfaatkan trace DevTools untuk melihat kapan hydration berubah, dan memastikan fallback yang konsisten untuk komponen Suspense. Tujuannya adalah membuat state terakhir sebelum hydration sejajar antara server dan klien agar UI stabil setelah deploy.
Memahami Penyebab Drift Hydration
Hydration drift terjadi ketika output HTML yang di-render di server tidak cocok dengan hasil rendering ulang di sisi client. Ketika App Router menerima halaman hasil SSR, React mencoba merekonsiliasi markup tersebut dengan tree komponen. Jika ada data asinkron yang menyelesaikan berbeda (misalnya API client yang menyertakan timestamp atau status dinamis), React akan memperbarui DOM, sehingga pengguna melihat re-render atau perbedaan visual.
Perbedaan ini juga bisa disebabkan oleh state lokal yang hanya dihitung di client. Saat komponen menginisialisasi nilai default berdasarkan window, localStorage, atau waktu, markup saat hydration tidak sesuai dengan markup server. Tujuan utama kita adalah memastikan data input yang memengaruhi markup sudah final sebelum server merender, atau setidaknya agar perbedaan tersebut dapat dideteksi dan ditangani secara sistematis.
Langkah Diagnostik Render Drift
1. Bandingkan state server vs client
Mulai dengan menyusun output data yang digunakan pada server dan client dalam bentuk debug log atau snapshot kecil. Misalnya, jika komponen menampilkan angka total, pastikan angka itu dikirim sebagai props dari generateMetadata atau async component di server dan bukan dihitung lagi di client dengan useEffect yang memanggil API berbeda.
Buat konsol log secara selektif:
export default async function Page() {
const serverData = await getServerData();
console.log('serverData', serverData);
return (
);
}
'use client';
export default function ClientSideComponent({ initialData }) {
useEffect(() => {
console.log('hydrated data', initialData);
}, [initialData]);
return {initialData.count};
}
Perhatikan apakah nilai yang dicetak saat server render sama dengan yang dibuat saat hydration. Jika berbeda, cari sumber perhitungan tambahan di client seperti memanggil fungsi dengan input yang belum stabil.
2. Gunakan DevTools Trace untuk melihat waktu hydration
Di Chrome DevTools, buka tab Performance dan rekam saat halaman dimuat. Filter untuk melihat peristiwa React untuk hydration. Jika ada rekonsiliasi yang memicu banyak pekerjaan setelah SSR, periksa komponen yang menyebabkan perubahan tersebut.
Trace ini membantu mengetahui apakah perbedaan terjadi selama hydration (update yang memicu rendering ulang) atau setelahnya (misalnya efek client). Fokus pada frame yang menunjukkan Hydration atau Suspense karena itu lokasi paling sering drift terjadi.
Menangani Data Asinkron dan Suspense
Data yang dimuat asinkron harus disinkronkan antara server dan client jika digunakan dalam markup utama. Dalam App Router, Anda dapat menunggu data sebelum render server dengan membuat komponen async atau memanfaatkan fetch dengan cache: 'force-cache'.
Untuk komponen Suspense, pastikan fallback yang ditampilkan sama di server dan client. Fallback yang berbeda dapat menyebabkan drift karena React menganggap tree berbeda.
Contoh sebelum dan sesudah perbaikan
Sebelum:
'use client';
import { useEffect, useState } from 'react';
export default function StatusBadge() {
const [status, setStatus] = useState('loading');
useEffect(() => {
api.getStatus().then(data => setStatus(data.state));
}, []);
return {status};
}
Hydration akan mismatch karena server mengirim status loading sementara client mengupdate menjadi hasil sesungguhnya. Setelah perbaikan:
export default async function StatusBadgeWrapper() {
const { state } = await api.getStatus();
return ;
}
'use client';
export function StatusBadge({ initialStatus }) {
const [status, setStatus] = useState(initialStatus);
useEffect(() => {
api.getStatus().then(data => {
if (data.state !== status) setStatus(data.state);
});
}, [status]);
return {status};
}
Pembaharuan ini memastikan server mengirimkan status final, sehingga hydration tidak berubah kecuali ada pembaruan data sesudahnya. Di sisi client kita masih mendukung pembaruan, tapi tidak merusak kondisi awal.
Strategi Fallback Konsisten
Susun fallback Suspense yang sama untuk server dan client. Hindari kondisi seperti:
- Fallback di server menampilkan loader panjang tapi di client hanya skeleton kecil.
- Fallback menggunakan data dinamis (misalnya membaca dari
localStorage) sehingga tampilannya berbeda saat hydration.
Jika fallback diperlukan, buat komponen statis yang tidak tergantung state runtime. Gunakan Suspense di level yang cukup tinggi sehingga seluruh subtree tetap konsisten. Tambahkan non-hydrating indikator (misalnya aria-live) untuk memberitahu pengguna tanpa mengubah struktur DOM secara drastis.
Checklist Verifikasi Setelah Deploy
Gunakan daftar berikut untuk memastikan UI tidak mengalami drift pasca deploy:
- Pastikan server dan client menerima props/state awal yang sama dengan membandingkan log atau snapshot JSON.
- Rekam sesi dengan DevTools Performance untuk melihat apakah hydration memicu update besar-besaran.
- Periksa Suspense fallback di berbagai jaringan; fallback harus sama di server dan client.
- Uji dengan data klien yang berubah-ubah (misalnya waktu lokal) untuk memastikan tidak ada pembaruan otomatis saat hydration.
- Monitoring log browser (konsol) dan analytics untuk mendeteksi re-renders berlebihan setelah pemuatan pertama.
Catatan Praktis dan Debugging Lanjutan
Trade-off: Menunggu data tambahan di server menambah waktu TTFB, tapi meminimalkan drift. Oleh karena itu, pertimbangkan caching dan batching untuk menghindari penundaan yang signifikan.
Kesalahan umum: Mengandalkan useEffect untuk menyiapkan markup utama menyebabkan perbedaan awal. Simpan useEffect hanya untuk pembaruan interaktif setelah hydration.
Debugging tip: Tambahkan log yang hanya berjalan di client (if (typeof window !== 'undefined')) untuk menyelidiki perbedaan kondisi yang terpicu setelah hydration dibandingkan dengan render awal di server.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!