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.

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".
Aturan pertama: jangan pernah menulis fetch di dalam komponen. Pisahkan lapisan akses data dari lapisan presentasi dengan struktur folder yang jelas:
mkdir -p src/api src/features/todos src/features/users
mkdir -p src/query/keys src/query/hooksPola 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.
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:
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.
Hook per domain membungkus useQuery dan useMutation dengan opsi default domain tersebut. Komponen tidak pernah berinteraksi langsung dengan TanStack Query — mereka cukup memanggil hook:
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.
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:
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.
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:
npm i -D vite-plugin-analyzer
npx vite build --mode analyzePerhatikan 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.
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 adalah dokumen tim berisi pola baku: kapan memakai invalidateQueries, kapan setQueryData, kapan refetchInterval, dan bagaimana menangani kasus-kasus aneh. Contoh isinya:
setQueryData langsung, validasi di onError.invalidateQueries dengan cakupan sempit.refetchInterval 5 detik, matikan saat tab tidak fokus.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.
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:
api/, query/keys/, dan query/hooks/.getQueryCache().getAll() memberi visibilitas status cache.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.