Episode ini membahas integrasi Zustand dengan framework modern: di mana store hidup di Next.js App Router, bagaimana menghindari perbedaan state antara server dan client saat hydration, serta kompatibilitas dengan React 19 termasuk hook use. Kalian juga membangun store per-scope dengan Context untuk pemilihan bahasa.

Zustand yang jalan mulus di client belum tentu aman di server. Episode 14 membahas integrasi Zustand dengan framework modern: di mana store hidup di Next.js App Router, bagaimana menghindari perbedaan state antara server dan client saat hydration, serta kompatibilitas dengan React 19 termasuk hook use. Kita juga membangun store per-scope dengan Context untuk kasus seperti pemilihan bahasa.
Kalian akan pulang dengan pola yang bisa langsung dipakai di project App Router dan framework serupa seperti Remix.
Di App Router, komponen secara default dirender di server. Store Zustand yang memakai localStorage atau sesi browser tidak boleh diakses selama render server. Pola aman: buat store di file stores/, pakai hanya di komponen yang ditandai 'use client', dan jangan panggil getState pada render server.
Pemisahan client dan server dengan marker:
'use client'
import { create } from 'zustand'
interface ThemeState {
theme: 'light' | 'dark'
toggleTheme: () => void
}
export const useThemeStore = create<ThemeState>()((set) => ({
theme: 'light',
toggleTheme: () =>
set((s) => ({ theme: s.theme === 'light' ? 'dark' : 'light' })),
}))'use client' memastikan modul ini hanya dieksekusi di client. Store yang dipakai di server murni atau Server Component harus dianggap terpisah dan tidak boleh membawa state sesi.
Masalah klasik SSR: server me-render state A, client me-render state B setelah rehydrate, menghasilkan mismatch. Solusinya menahan render sampai hydration selesai:
import { useEffect, useState } from 'react'
function useHydrated() {
const [hydrated, setHydrated] = useState(false)
useEffect(() => setHydrated(true), [])
return hydrated
}
export function ThemeToggle() {
const hydrated = useHydrated()
const theme = useThemeStore((s) => s.theme)
if (!hydrated) {
return <div className="placeholder" />
}
return <button onClick={() => useThemeStore.getState().toggleTheme()}>{theme}</button>
}useHydrated() mengembalikan false saat render server dan render pertama di client, lalu berubah true setelah effect berjalan. Selama belum hydrated, komponen merender placeholder yang identik antara server dan client.
React 19 memperkenalkan hook use yang bisa dipanggil di dalam condition. Zustand v5 menyediakan fungsi useStore yang memanfaatkannya untuk membaca store tanpa hook biasa:
import { use } from 'react'
import { useStore } from 'zustand'
export function useZustandStore(store, selector) {
return useStore(store, selector)
}
export function CountBadge() {
const count = useZustandStore(useCounter, (s) => s.count)
return <span>{count}</span>
}useStore(store, selector) menerima store yang dibuat createStore dan selector apa pun, mengikuti pola use(store). Ini membuat Zustand selaras dengan model concurrent rendering di React 19 tanpa kehilangan re-render selektif.
Terkadang store harus terisolasi per scope, misalnya bahasa aktif per halaman atau form per wizard. Kombinasikan Context untuk menyimpan instance store dengan Zustand untuk isi state:
const LanguageContext = createContext(null)
export function LanguageProvider({ lang, children }) {
const store = useMemo(() => createLanguageStore(lang), [lang])
return (
<LanguageContext.Provider value={store}>
{children}
</LanguageContext.Provider>
)
}
export function useLanguage() {
const store = useContext(LanguageContext)
return useStore(store, (s) => s.lang)
}createContext(null) menyimpan instance store per provider. Setiap scope mendapat store sendiri, sementara komponen tetap memakai hook useStore biasa — gabungan kecepatan Context untuk isolation dan selektivitas Zustand.
Episode 14 membawa Zustand ke dunia server rendering dan React 19: store hanya di client, menahan render sampai hydration selesai, fungsi useStore yang kompatibel dengan hook use, serta store per-scope lewat Context.
Inti yang harus dibawa pulang:
Di episode 15 selanjutnya kita akan membahas security dan best practice state — melindungi data sensitif di store yang ter-persist, memilih storage yang aman, mengklasifikasikan state, dan mengontrol akses berbasis role di UI.