Membedakan client state dan server state, menerapkan pola data fetching optimal di Next.js dengan cache dan revalidation, serta memakai TanStack Query untuk data yang hidup di sisi browser.

Hampir semua bug "data tidak muncul", "halaman lambat", atau "UI tidak sinkron" di aplikasi fullstack berakar dari satu kesalahan: menempatkan data pada lapisan yang salah. Episode ini membahas state & data fetching — membedakan client state vs server state, memakai caching dan revalidation dengan benar, serta tools yang tepat untuk tiap kasus.
Pikirkan state sebagai dua dunia: server state adalah kebenaran di database, client state hanyalah cerminan sementara di browser. Kebingungan dimulai ketika developer memperlakukan keduanya sebagai satu hal.
| Aspek | Client State | Server State |
|---|---|---|
| Contoh | useState (modal terbuka, input form) | Data user, daftar note dari database |
| Dimiliki oleh | Komponen di browser | Server/database |
| Kebutuhan | Rendering cepat tanpa network | Sinkronisasi dengan sumber kebenaran |
| Kesalahan umum | Menyimpan data server di state dan menduplikasinya | Menganggap data client selalu segar |
Aturan emas: server state tidak perlu hidup di useState/useEffect. Jika data berasal dari database, biarkan server mengirimkannya dan cache di layer yang tepat — bukan menyalinnya ke state lokal yang akan cepat basi.
Next.js memberi tiga cara membaca data, dan masing-masing punya perilaku cache yang berbeda:
fetch di Server Component di-cache secara default oleh Next.js. Data yang sama dipakai bersama lintas request:
export default async function Page() {
const res = await fetch("https://api.example/posts")
const posts = await res.json()
return <PostList posts={posts} />
}Data yang berubah jarang (misal daftar artikel) bisa di-revalidate otomatis tiap interval:
const res = await fetch("https://api.example/posts", {
next: { revalidate: 60 },
})Data yang berubah saat ada aksi (misal catatan baru) di-revalidate lewat revalidatePath — yang sudah kita pakai sejak episode 3:
import { revalidatePath } from "next/cache"
await prisma.note.create({ data: { title, body } })
revalidatePath("/notes")Lapisan cache inilah yang menyelesaikan masalah N+1 dari episode 4: query yang sama diulang berkali-kali kini hanya dijalankan sekali, lalu disajikan dari cache sampai di-revalidate.
Important
Kebingungan terbesar developer Next.js 2026 adalah cache yang "tidak segar". Ingat tiga kata kunci: default (cache, dibersihkan saat deploy), revalidate (interval waktu), dan revalidatePath/revalidateTag (event). Pilih yang sesuai dengan seberapa sering data berubah — jangan serba default, jangan juga menonaktifkan cache semua.
Ada data yang memang harus hidup di client — data yang berubah real-time dan dipakai lintas komponen. Untuk itu, jangan menulis cache manual; pakai TanStack Query yang menangani caching, retry, dan background refetch:
pnpm add @tanstack/react-query"use client"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
const client = new QueryClient()
export default function Provider({ children }: { children: React.ReactNode }) {
return <QueryClientProvider client={client}>{children}</QueryClientProvider>
}"use client"
import { useQuery } from "@tanstack/react-query"
export default function NoteStats() {
const { data, isLoading, error } = useQuery({
queryKey: ["notes", "stats"],
queryFn: async () => {
const res = await fetch("/api/notes/stats")
if (!res.ok) throw new Error("Gagal mengambil statistik")
return res.json()
},
})
if (isLoading) return <p>Memuat...</p>
if (error) return <p>Terjadi kesalahan</p>
return <p>Total catatan: {data.total}</p>
}TanStack Query otomatis: cache hasil, retry saat gagal, dan refetch di background saat tab kembali fokus. Setelah mutasi, panggil invalidateQueries agar data terkait di-refetch:
const queryClient = useQueryClient()
await queryClient.invalidateQueries({ queryKey: ["notes"] })| Skenario | Pilih |
|---|---|
| Halaman pertama dimuat, SEO penting | Server Component + cache/revalidate |
| Data berubah dari aksi server (form) | Server Action + revalidatePath |
| Data berubah real-time di client | TanStack Query (WebSocket/SSE, episode 14) |
| State UI sementara (modal, tab) | useState — jangan sentuh server |
| Data lintas komponen yang jarang berubah | Context + useMemo, atau TanStack Query |
Aturannya sederhana: sebisa mungkin di server, sisanya di client dengan query cache — dan state UI murni tetap di useState.
Selesaikan dengan mengaudit aplikasi catatan kalian:
/notes membaca via Prisma di server component (bukan useEffect).revalidatePath di setiap server action yang menulis data.fetch dengan revalidate: 60 atau TanStack Query.useEffect untuk memuat data di aplikasi.curl -s -o /dev/null -w "%{time_total}s\n" http://localhost:3000/notes
curl -s -o /dev/null -w "%{time_total}s\n" http://localhost:3000/notesRequest kedua harus jauh lebih cepat — bukti cache bekerja.
Inti yang harus dibawa pulang:
useState/useEffect — gunakan cache server.Di episode 9 selanjutnya kita membahas testing fullstack — unit test untuk API dan UI, integration test, dan end-to-end dengan Playwright — agar kalian bisa mengubah kode tanpa takut merusak yang lain. Sampai jumpa di episode 9!