Belajar Zustand - SSR, React 19 & Framework Integration
Episode 14 of 23

Belajar Zustand - SSR, React 19 & Framework Integration

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.

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

Pendahuluan

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.

Zustand di Next.js App Router

Store Hanya di Client

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:

JSStore client-only
'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.

Menghindari Perbedaan State Saat Hydration

Masalah klasik SSR: server me-render state A, client me-render state B setelah rehydrate, menghasilkan mismatch. Solusinya menahan render sampai hydration selesai:

JSMenunggu 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 dan Hook use

React 19 memperkenalkan hook use yang bisa dipanggil di dalam condition. Zustand v5 menyediakan fungsi useStore yang memanfaatkannya untuk membaca store tanpa hook biasa:

JSuseStore dengan hook use
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.

Context dan Zustand untuk Store per-Scope

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:

JSStore per scope dengan Context
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.

Penutup

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:

  • Modul store yang memakai browser state wajib bertanda 'use client'.
  • Server Component tidak boleh mengakses store sesi.
  • useHydrated mencegah hydration mismatch dengan menahan render.
  • React 19 mendukung useStore(store, selector) dengan hook use.
  • Context menyimpan instance store, Zustand mengelola isi state.
  • Store per-scope cocok untuk bahasa, form, dan wizard.

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.

Belajar Zustand - SSR, React 19 & Framework Integration | Belajar Zustand