Belajar TanStack Query - Performance & Cache Tuning
Episode 13 of 23

Belajar TanStack Query - Performance & Cache Tuning

Episode ini membahas cara menurunkan jumlah fetch: staleTime global versus per-query, keepPreviousData, dan normalisasi cache. Kalian juga belajar Suspense mode dengan useSuspenseQuery dan React Suspense untuk loading yang granular.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Pendahuluan

TanStack Query sudah otomatis efisien, tapi "otomatis" bukan berarti tidak bisa dibuat lebih baik. Aplikasi dengan puluhan query bisa menghasilkan ratusan request yang sebenarnya tidak perlu — karena staleTime terlalu kecil, refetch yang terlalu agresif, atau desain query key yang tidak efisien.

Episode 13 membahas tuning performa: mengatur staleTime secara strategis, memakai keepPreviousData, dan menormalkan struktur cache. Lalu kita masuk ke Suspense mode, cara modern menangani loading yang terintegrasi dengan React Suspense.

Mengurangi Fetch Berlebih

staleTime Global vs Per-Query

Keputusan pertama: di mana set staleTime? Konfigurasi global di QueryClient memberi baseline, tapi data yang berbeda butuh kesegaran yang berbeda.

JSstaleTime global dan per-query
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 30 * 1000,
    },
  },
})
 
// per-query: data hampir statis
useQuery({
  queryKey: ["config"],
  queryFn: fetchConfig,
  staleTime: 60 * 60 * 1000,
})

staleTime: 30 * 1000 di global menjadi dasar semua query, dan query config menimpanya menjadi 1 jam karena datanya hampir tidak berubah. staleTime yang lebih besar berarti lebih sedikit request. Mulai dari global yang moderat, lalu sesuaikan per query yang paling sering di-mount.

Keep Previous Data untuk Navigasi

keepPreviousData (dari episode 9) bukan hanya untuk pagination — dia berguna untuk UI yang berpindah dari satu item ke item lain, seperti detail yang berganti cepat. Data lama tetap tampil, query baru berjalan di latar belakang, dan user tidak pernah melihat layar kosong.

JSkeepPreviousData untuk detail
useQuery({
  queryKey: ["todos", id],
  queryFn: () => fetchTodo(id),
  placeholderData: keepPreviousData,
})

placeholderData: keepPreviousData menjaga konten tetap tampil saat id berubah. keepPreviousData membuat navigasi antar item terasa mulus tanpa spinners yang mengganggu.

Normalisasi Cache

Salah satu sumber duplikasi data: cache menyimpan objek todo yang sama berkali-kali — satu di list, satu di detail, satu di setiap query lain. Normalisasi menyimpan entitas di satu tempat dengan setQueryData, lalu query lain cukup mereferensi id-nya:

JSMenyimpan entitas ternormalisasi
queryClient.setQueryData(["entities", "todos"], (old) => ({
  ...(old ?? {}),
  [todo.id]: todo,
}))

["entities", "todos"] menjadi gudang tunggal semua todo, dikunci oleh id. queryClient.setQueryData(["entities", "todos"], ...) mempermudah invalidasi terpusat dan mengurangi duplikasi memori. Normalisasi penuh biasanya dibawa oleh library state seperti Redux Toolkit — episode 16 akan membahas integrasinya.

Suspense Mode

Konsep React Suspense

React Suspense membiarkan komponen "menunggu" tanpa menulis logic loading manual. Saat sebuah query ditangguhkan, React menampilkan fallback yang didefinisikan di boundary:

JSBoundary Suspense
import { Suspense } from "react"
 
<Suspense fallback={<p>Memuat todos...</p>}>
  <Todos />
</Suspense>

Ketika Todos menunggu query, React menampilkan fallback sampai data siap. Suspense menggantikan pemeriksaan isLoading manual — komponen bisa menulis kode seolah data selalu tersedia.

useSuspenseQuery

Untuk memakai Suspense, ganti useQuery dengan useSuspenseQuery:

JSuseSuspenseQuery
import { useSuspenseQuery } from "@tanstack/react-query"
 
function Todos() {
  const { data } = useSuspenseQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  })
 
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

useSuspenseQuery melempar Promise saat data belum ada, dan React Suspense menangkapnya untuk menampilkan fallback. useSuspenseQuery mengembalikan data yang selalu tersedia ketika kode berjalan — tidak ada isLoading di dalam komponen, dan error ditangani oleh Error Boundary.

Loading Granular dengan Banyak Boundary

Kekuatan Suspense terlihat ketika fallback dibuat granular per bagian halaman, bukan satu spinner besar:

JSLoading per bagian
<Suspense fallback={<p>Memuat user...</p>}>
  <UserHeader />
</Suspense>
<Suspense fallback={<p>Memuat todos...</p>}>
  <Todos />
</Suspense>

Dua boundary terpisah berarti header dan list memuat secara independen. Suspense per bagian memberi kalian kontrol penuh atas pengalaman loading — bagian yang cepat tampil duluan, bukan menunggu seluruh halaman.

Tip

Suspense mode menghilangkan pemeriksaan isLoading manual, tapi jangan lupa menangani error dengan Error Boundary. TanStack Query melempar error dari useSuspenseQuery ke boundary terdekat, jadi pastikan boundary ada di sekitar area yang memakai hook ini.

Penutup

Episode 13 meningkatkan efisiensi aplikasi dari dua arah: mengurangi request dengan tuning staleTime, keepPreviousData, dan normalisasi cache, lalu menyederhanakan loading dengan Suspense mode dan useSuspenseQuery.

Inti yang harus dibawa pulang:

  • staleTime global sebagai baseline; per-query untuk data khusus.
  • keepPreviousData menghilangkan flicker saat berpindah data.
  • Normalisasi cache menyimpan entitas sekali dan mereferensikan id.
  • React Suspense menampilkan fallback sementara query menunggu.
  • useSuspenseQuery mengembalikan data tanpa status isLoading.
  • Boundary per bagian memberi loading yang granular.

Di episode 14 selanjutnya kita akan membahas SSR dan framework integration — hydrate cache di server dengan dehydrate dan HydrationBoundary untuk Next.js App Router, prefetch di server, serta integrasi dengan Remix loaders dan pola framework-agnostic.

Belajar TanStack Query - Performance & Cache Tuning | Belajar TanStack Query