RIZKYEMHA
โ† Back to Blog๐Ÿ‡ฌ๐Ÿ‡ง English
ZustandNext.jsReactState ManagementNode.js

Kenapa Zustand Perlu Context di Next.js?

5 Juni 2026ยท8 min read
Kenapa Zustand Perlu Context di Next.js?

Kenapa Zustand Perlu Context di Next.js?

Pertanyaan yang muncul saat aku membaca dokumentasi โ€” dan ternyata jawabannya bukan soal React, tapi soal cara Node.js bekerja.

Waktu pertama kali aku membaca dokumentasi Zustand untuk Next.js, ada satu hal yang langsung bikin aku berhenti dan bertanya-tanya. Di sana tertulis bahwa store tidak boleh didefinisikan sebagai global variable, dan harus dibungkus dengan Context Provider. Reaksiku waktu itu: kenapa? Bukankah Zustand memang dirancang sebagai state management global?

Aku sudah cukup familiar dengan Zustand di aplikasi React biasa. Bikin store, ekspor hook-nya, pakai di mana saja. Simpel. Tapi begitu masuk ke territory Next.js โ€” terutama App Router โ€” tiba-tiba ada lapisan tambahan yang terasa membingungkan. Aku putuskan untuk menggali lebih dalam sampai aku benar-benar paham kenapa.


Akar masalahnya bukan di React

Setelah aku telusuri, ternyata pertanyaan ini sama sekali bukan tentang React atau Zustand secara spesifik. Ini tentang cara Node.js โ€” runtime yang menjalankan server Next.js โ€” mengelola memori.

Di browser, setiap tab punya JavaScript environment sendiri. Kalau kamu buka dua tab, mereka tidak berbagi variabel sama sekali. Tapi di server Node.js, situasinya berbeda total: satu process melayani semua request dari semua user.

Browser:  Tab A โ†’ JS env A (isolasi)
          Tab B โ†’ JS env B (isolasi)

Node.js:  Request A โ†’ process yang sama
          Request B โ†’ process yang sama  โ† shared!

Dan yang membuat ini makin kritis: Node.js melakukan module caching. Artinya, setiap file yang di-import hanya dieksekusi sekali saat server pertama kali start. Setelah itu, semua request menggunakan instance yang sama dari module tersebut.


Bagaimana kebocoran data bisa terjadi

Mari aku ilustrasikan dengan skenario konkret. Bayangkan kita punya store Zustand yang dibuat di level module:

// store.ts โ€” cara yang BERBAHAYA di SSR
export const useStore = create(() => ({
  user: null,
  cart: [],
}))

File ini hanya dieksekusi sekali. Instance store-nya hidup sepanjang server hidup. Sekarang bayangkan ini terjadi:

t=0ms โ†’ User A login โ†’ set({ user: "Alice", cart: ["item1"] })
         โ†“ store singleton diupdate

t=5ms โ†’ User B request โ†’ getState() โ†’ { user: "Alice" }
         โ†‘ User B dapat data User A!

Ini bukan teori. Ini memang cara kerja Node.js. Dan yang bikin ngeri, bug seperti ini sangat susah di-debug karena tidak selalu konsisten โ€” tergantung timing request masuk dan keluar.


Mengapa Context jadi solusinya

Begitu aku paham masalahnya, solusinya jadi masuk akal dengan sendirinya. Yang kita butuhkan adalah cara untuk membuat instance store baru untuk setiap request โ€” bukan memakai satu instance yang dishare semua orang.

Di sinilah Context berperan. Tapi bukan karena alasan yang biasanya kita pakai Context di React (menghindari prop drilling). Di sini, Context dipakai karena satu alasan spesifik: lifecycle-nya terikat pada component render, bukan pada module.

Request A masuk โ†’ StoreProvider dirender โ†’ createStore() โ†’ instance A
                  HTML selesai dikirim   โ†’ instance A dibuang

Request B masuk โ†’ StoreProvider dirender โ†’ createStore() โ†’ instance B
                  HTML selesai dikirim   โ†’ instance B dibuang

Setiap request dapat "loker" sendiri. Ketika request selesai, loker itu dikembalikan dan isinya tidak pernah terlihat oleh request lain.


Implementasi sesuai dokumentasi resmi

Satu hal yang aku perlu luruskan juga: di dokumentasi resmi Zustand, store di dalam Provider menggunakan useState, bukan useRef seperti yang kadang aku lihat di artikel lain.

// src/providers/counter-store-provider.tsx
"use client"
 
import { type ReactNode, createContext, useState, useContext } from "react"
import { useStore } from "zustand"
import { type CounterStore, createCounterStore } from "@/stores/counter-store"
 
export const CounterStoreContext = createContext(null)
 
export const CounterStoreProvider = ({ children }: { children: ReactNode }) => {
  // useState dengan initializer function โ†’ store dibuat sekali saat mount
  const [store] = useState(createCounterStore)
 
  return (
    <CounterStoreContext.Provider value={store}>
      {children}
    </CounterStoreContext.Provider>
  )
}

Alasan docs resmi memilih useState bukan useRef: keduanya secara hasil sama-sama membuat store sekali saja, tapi useState dengan initializer function lebih idiomatik di React dan lazy by default โ€” createCounterStore hanya dipanggil pada render pertama, tidak pada setiap re-render.

Store-nya sendiri dipisah sebagai factory function, bukan hook langsung:

// src/stores/counter-store.ts
import { createStore } from "zustand"
 
export type CounterStore = {
  count: number
  incrementCount: () => void
  decrementCount: () => void
}
 
export const createCounterStore = () =>
  createStore<CounterStore>()((set) => ({
    count: 0,
    incrementCount: () => set((state) => ({ count: state.count + 1 })),
    decrementCount: () => set((state) => ({ count: state.count - 1 })),
  }))

Kapan ini benar-benar perlu?

Setelah semua ini, ada satu pertanyaan praktis: kapan kita benar-benar harus repot-repot melakukan ini?

KondisiPerlu Context?
State UI murni di client componentsTidak perlu
State diinisialisasi dari server (cookies, session)Perlu
State berbeda per user/requestPerlu
Store menyimpan data sensitif (auth, cart)Perlu
State global yang sama untuk semua user (config, i18n)Opsional

Catatan dari diskusi komunitas Zustand: Kalau kamu hanya menggunakan Zustand di client components dan state-nya tidak bergantung pada data server, global store tetap aman. Yang berbahaya adalah ketika state menyentuh data per-user dan dipakai di sisi server.


Apa yang aku pelajari

Perjalanan memahami ini mengajarkan aku bahwa banyak "aturan" di Next.js App Router sebenarnya bukan tentang React sama sekali โ€” melainkan tentang memahami bahwa Next.js adalah aplikasi server yang berjalan di Node.js, dengan segala konsekuensi arsitekturalnya.

Context dalam konteks ini bukan alat untuk berbagi state seperti biasanya. Ia adalah mekanisme untuk mengisolasi state โ€” memastikan setiap request punya ruangnya sendiri, dan tidak ada data yang bocor ke pengguna lain.

Begitu perspektif itu berubah, dokumentasi Zustand yang tadinya terasa aneh jadi sangat masuk akal. Dan lebih dari itu: aku jadi lebih hati-hati setiap kali menulis variabel di level module di dalam aplikasi Next.js.

โ† All ArticlesShare on X โ†—