Belajar TanStack Query - Production-Ready Architecture
Episode 21 of 23

Belajar TanStack Query - Production-Ready Architecture

Episode ini membahas arsitektur TanStack Query level produksi untuk skala tim: struktur folder query modules, katalog query keys, custom hooks per domain, konvensi naming, monitoring, bundle size, SSR tuning, serta runbook caching dan invalidasi.

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

Pendahuluan

Menulis query yang bekerja untuk satu komponen itu mudah. Menjaga konsistensi cache di aplikasi besar yang dikerjakan banyak developer itu cerita lain. Tanpa konvensi, query keys jadi kacau, queryFn diduplikasi di tiga tempat, dan invalidasi menembak key yang salah secara diam-diam.

Episode 21 membahas arsitektur production-ready: struktur folder query modules, katalog query keys terpusat, custom query hooks per domain, hingga monitoring, bundle size, SSR tuning, dan runbook caching yang bisa dipakai tim. Ini episode untuk naik level dari "query yang bekerja" menjadi "sistem server state yang bisa dirawat bertahun-tahun".

Struktur Folder Query Modules

Pemisahan Api dan Hooks

Aturan pertama: jangan pernah menulis fetch di dalam komponen. Pisahkan lapisan akses data dari lapisan presentasi dengan struktur folder yang jelas:

Struktur query modules
mkdir -p src/api src/features/todos src/features/users
mkdir -p src/query/keys src/query/hooks

Pola yang umum dipakai:

  • src/api/ menampung query functions murni — hanya fetch dan parse respons.
  • src/query/keys/ menampung katalog query keys terpusat.
  • src/query/hooks/ menampung custom hooks per domain.
  • src/features/<domain>/ menampung komponen UI yang memakai hooks tersebut.

api/ tidak boleh memakai TanStack Query sama sekali — fungsi di sana menerima argumen dan mengembalikan Promise. fetchTodos(id) dan createTodo(payload) adalah contoh query functions yang bisa diuji tanpa render komponen. Lapisan ini juga bisa dibagi dengan backend developer atau di-generate dari OpenAPI.

Konvensi Naming

Nama fungsi mengikuti pola yang bisa dinalar semua orang: fetchX untuk baca, createX dan updateX untuk tulis, deleteX untuk hapus. Custom hooks diberi nama sesuai domain: useTodos, useTodoById, useCreateTodo. Query keys mengikuti hierarki domain, lalu resource, lalu modifikasi:

JSKatalog query keys terpusat
export const todoKeys = {
  all: ["todos"] as const,
  lists: () => [...todoKeys.all, "list"] as const,
  list: (filters) => [...todoKeys.lists(), { filters }] as const,
  details: () => [...todoKeys.all, "detail"] as const,
  detail: (id) => [...todoKeys.details(), id] as const,
}

Katalog todoKeys membuat seluruh aplikasi memakai key yang sama. todoKeys.detail(id) menghasilkan array yang identik di hook, di prefetch, dan di invalidasi. Tidak ada lagi string "todos" yang diketik manual di tiga file berbeda dengan risiko typo.

Custom Query Hooks per Domain

Hook per domain membungkus useQuery dan useMutation dengan opsi default domain tersebut. Komponen tidak pernah berinteraksi langsung dengan TanStack Query — mereka cukup memanggil hook:

JSCustom hook per domain
export function useTodos(filters) {
  return useQuery({
    queryKey: todoKeys.list(filters),
    queryFn: () => fetchTodos(filters),
    staleTime: 5 * 60 * 1000,
  })
}
 
export function useCreateTodo() {
  const queryClient = useQueryClient()
 
  return useMutation({
    mutationFn: createTodo,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: todoKeys.all })
    },
  })
}

Manfaatnya besar. Jika tim memutuskan menaikkan staleTime untuk seluruh todo, kalian cukup mengubah satu tempat. Jika endpoint berubah, cukup di api/. Komponen menjadi tipis dan fokus pada presentasi — useTodos(filters) dan useCreateTodo() adalah satu-satunya API yang mereka kenal.

Monitoring dan Bundle Size

Melacak Health Cache

Di produksi, kalian butuh visibilitas: berapa banyak query yang di-refetch, berapa yang gagal, dan apakah cache membesar tidak terkendali. QueryCache menyediakan getAll() untuk mengintip seluruh query aktif:

JSMonitoring cache secara programatik
export function reportQueryCache(client) {
  const queries = client.getQueryCache().getAll()
 
  return queries.map((query) => ({
    key: query.queryKey,
    state: query.state.status,
    observers: query.getObserversCount(),
  }))
}

getAll() mengembalikan daftar query beserta statusnya. Gabungkan dengan error tracking (misalnya Sentry) untuk mengirim laporan ketika ada query gagal berulang kali, dan dengan network log untuk melihat pola refetch yang tidak wajar. Data ini membantu tim menemukan staleTime yang terlalu kecil atau invalidasi yang terlalu agresif.

Menjaga Bundle Tetap Ramping

TanStack Query v5 dirancang tree-shakeable: hanya import yang kalian pakai yang masuk ke bundle. Gunakan import bernama, jangan pernah import seluruh paket. Jalankan bundle analyzer secara berkala dan pantau ukuran @tanstack/query-core di laporan. Hindari memuat devtools di produksi:

Cek ukuran bundle devtools
npm i -D vite-plugin-analyzer
npx vite build --mode analyze

Perhatikan juga baris ini di komponen utama — pastikan devtools hanya dirender di development, karena @tanstack/react-query-devtools menambah beban yang tidak diperlukan di production build.

SSR Tuning dan Runbook Caching

Menghindari Hydration Mismatch

Untuk aplikasi Next.js atau Remix, tune SSR dengan tiga aturan: buat QueryClient baru per request di server, prefetch dengan prefetchQuery, lalu dehydrate dan hydrate lewat HydrationBoundary. Aturan pertama adalah yang paling sering dilanggar — QueryClient yang dibagi antar request akan membocorkan data user A ke user B.

Setelah hydrasi, data yang di-prefetch di server dianggap fresh berkat staleTime. Jangan set staleTime ke 0 untuk halaman yang sudah di-render server, karena UI akan memicu refetch sia-sia segera setelah halaman terlihat.

Runbook Caching dan Invalidasi

Runbook adalah dokumen tim berisi pola baku: kapan memakai invalidateQueries, kapan setQueryData, kapan refetchInterval, dan bagaimana menangani kasus-kasus aneh. Contoh isinya:

  • Data form: setQueryData langsung, validasi di onError.
  • Agregat yang dihitung server: invalidateQueries dengan cakupan sempit.
  • Polling status job: refetchInterval 5 detik, matikan saat tab tidak fokus.
  • Update dari webhook: invalidasi dari event handler (episode 19).

Runbook mencegah setiap developer menemukan kembali cara yang berbeda untuk masalah yang sama. Dengan katalog key, custom hooks, dan runbook yang konsisten, arsitektur server state bisa dipelihara oleh siapa pun di tim tanpa pelatihan ulang.

Penutup

Episode 21 menyatukan semua episode sebelumnya menjadi sebuah sistem: folder query modules yang rapi, katalog query keys terpusat, custom hooks per domain, monitoring cache, bundle size yang terkendali, dan runbook yang membuat tim bekerja dengan satu pola. Inilah arsitektur yang bertahan dari demo hingga ribuan request per menit.

Inti yang harus dibawa pulang:

  • Pisahkan api/, query/keys/, dan query/hooks/.
  • Katalog query keys menghilangkan string duplikat dan typo.
  • Custom hooks per domain menyembunyikan detail TanStack Query dari komponen.
  • getQueryCache().getAll() memberi visibilitas status cache.
  • Tree-shaking dan devtools yang tidak ikut produksi menjaga bundle ramping.
  • Runbook menyepakati kapan memakai invalidasi, setQueryData, dan polling.

Di episode 22, episode terakhir series, kita akan membahas ekosistem alternatif dan refleksi akhir — perbandingan TanStack Query dengan SWR, RTK Query, Apollo, dan useEffect manual, plus rekap journey kalian dari episode 0 sampai 21.