Render mismatch saat menggunakan React Query dengan Next.js muncul karena state yang dirender di server tidak identik saat browser melakukan hydration. Sinkronisasi cache adalah inti solusinya: pastikan data yang sudah diprefetch di server tersedia persis saat klien mulai menjalankan aplikasi sehingga React tidak membuang DOM yang sudah dihasilkan.

Dalam artikel ini kita akan membedah penyebab mismatch, langkah debugging yang efektif, strategi mengirim dan menerima cache, serta checklist preventif agar UI tidak flicker atau gagal mount.

Memahami Penyebab Render Mismatch pada React Query SSR

Perbedaan Cache antara Server dan Client

React Query menyimpan data dalam cache QueryClient. Saat server melakukan SSR, QueryClient berisi data hasil fetch yang kemudian diserialisasi dan dikirim bersama HTML. Di sisi klien, React Query membangun QueryClient baru. Jika cache yang direhydrasi tidak sama persis atau data sudah berubah, maka saat React membandingkan markup server dengan virtual DOM saat klien-hydrate akan terjadi mismatch.

Serialisasi Cache dan Staging State

Cache React Query memiliki metadata seperti timestamp dan isFetching. Serialisasi default hanya menyertakan data dasar, jadi saat klien menghidupkan kembali cache, staging state seperti loading flag mungkin berbeda. Selain itu, data server bisa menjadi usang jika terjadi update di antara render server dan hydration klien. Ketidaksamaan ini menghasilkan perbedaan props, dan React akan menolak mount awalnya.

Hydration Timing dan Data yang Berubah

Mismatch sering terjadi ketika data terus berubah (misalnya real-time feed). Server render menggunakan snapshot tertentu, tetapi begitu klien siap, React Query mungkin memicu refetch otomatis karena staleTime habis atau window focus. Saat data baru masuk, markup berubah sebelum hydration selesai.

Strategi Sinkronisasi Cache React Query

Gunakan dehydrate dan hydrate secara Konsisten

React Query sudah menyediakan utilitas untuk menyalin cache antar environment: dehydrate(queryClient) di server dan hydrate(queryClient, pageProps.dehydratedState) di klien. Pastikan seluruh query yang diperlukan sudah prefetch sebelum dehydrate dipanggil.

export async function getServerSideProps() {
  const queryClient = new QueryClient();
  await queryClient.prefetchQuery(['users'], fetchUsers);
  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  };
}

function UsersPage({ dehydratedState }) {
  const queryClient = useQueryClient();
  hydrate(queryClient, dehydratedState);
  const { data } = useQuery(['users'], fetchUsers);
  return ;
}

Jika Anda menggunakan Hydrate wrapper dari React Query, cukup pasangkan dehydratedState ke Hydrate agar seluruh cache terbarui sekaligus.

Manual Rehydration dan Cache Invalidations

Dalam kasus kompleks (misalnya data tak terduga pada client), pertimbangkan membandingkan snapshot cache. Anda bisa menyimpan snapshot sebelum serialize dan memeriksa ulang di client, lalu memanggil queryClient.setQueryData secara manual jika perlu.

Lakukan invalidation hanya setelah hydration selesai untuk menghindari refetch yang mengubah data terlalu cepat. Gunakan flag seperti enabled: typeof window !== 'undefined' saat query harus dimulai setelah klien siap.

Strategi Key dan Scope Cache

Pilih key query yang deterministik dan konsisten antara server dan client. Hindari menambahkan state lokal ke key, karena akan menyebabkan cache berbeda saat render server dan client. Jika Anda menggunakan route dinamis, pastikan parameter route dimasukkan ke key sebelum prefetch.

Langkah Debugging Render Mismatch

Interpretasi mismatch memerlukan perbandingan state server dan klien:

  • Log server vs client: Cetak payload query sebelum dehydrate dan setelah hydrate dalam console untuk melihat perbedaan atribut.
  • Snapshot state: Simpan snapshot JSON dari queryClient.getQueriesData() di server dan bandingkan dengan queryClient.getQueryData() di client setelah hydrate.
  • Periksa markup: Gunakan React DevTools untuk melihat komponen mana yang tidak cocok dan atribut mana yang berubah.
  • Nonaktifkan refetch otomatis sementara: Set refetchOnWindowFocus dan refetchOnMount ke false agar data tidak berubah selama debugging.

Checklist Preventif Agar UI Tidak Flicker atau Gagal Mount

  • Prefetch lengkap sebelum dehydrate: Pastikan semua query penting sudah selesai untuk menghindari cache kosong.
  • Gunakan Hydrate di root app: Jika hanya sebagian komponen yang diberi state, bagian lain masih bisa mismatch.
  • Jaga ukuran cache wajar: Menyerialisasi data terlalu besar meningkatkan waktu hydration; pertimbangkan selective cache.
  • Selaraskan timestamp: Jika server memberi data real-time, set initialDataUpdatedAt agar klien tahu kapan data dibuat.
  • Beri fallback loading minimal: Render skeleton yang identik di server dan client agar perubahan data tidak terlihat.

Dengan menerapkan langkah-langkah di atas, render mismatch berkurang drastis karena cache tetap konsisten, refetch terkendali, dan React dapat melakukan match antara markup server dan virtual DOM klien.