{"record":{"id":"9e2da92f4da58e2c","repo":"pmndrs/zustand","slug":"cannot-set-state-of-zustand-store-in-ssr","errorCode":null,"errorMessage":"Cannot set state of Zustand store in SSR","messagePattern":"Cannot set state of Zustand store in SSR","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/middleware/ssrSafe.ts","lineNumber":20,"sourceCode":"\n// This is experimental middleware. It will be changed before finalizing it.\n// https://github.com/pmndrs/zustand/discussions/2740\n// TODO Not very happy with the middleware name. Will revisit it later.\nexport function ssrSafe<\n  T extends object,\n  U extends object,\n  Mps extends [StoreMutatorIdentifier, unknown][] = [],\n  Mcs extends [StoreMutatorIdentifier, unknown][] = [],\n>(\n  config: StateCreator<T, Mps, Mcs, U>,\n  isSSR: boolean = typeof window === 'undefined',\n): StateCreator<T, Mps, Mcs, U> {\n  return (set, get, api) => {\n    if (!isSSR) {\n      return config(set, get, api)\n    }\n    const ssrSet = () => {\n      throw new Error('Cannot set state of Zustand store in SSR')\n    }\n    api.setState = ssrSet\n    return config(ssrSet as never, get, api)\n  }\n}\n","sourceCodeStart":2,"sourceCodeEnd":26,"githubUrl":"https://github.com/pmndrs/zustand/blob/beca84e600e4e250f6b244d22878e72948f331c7/src/middleware/ssrSafe.ts#L2-L26","documentation":"This error is thrown by Zustand's experimental `ssrSafe` middleware (exported as `unstable_ssrSafe`). The middleware detects server-side rendering (via `typeof window === 'undefined'` or the explicit `isSSR` arg) and, when true, replaces the store's `set` function and `api.setState` with a stub that throws. Its purpose is to fail fast: any state mutation during SSR would produce a hydration mismatch or pollute shared server state, so the middleware surfaces the offending call site immediately instead of letting it silently corrupt the render.","triggerScenarios":"A store created with `create<T>()(unstable_ssrSafe(...))` whose `set()` or `api.setState()` is invoked while `typeof window === 'undefined'`. Concretely: (a) a store action calling `set(...)` is invoked from Next.js `getServerSideProps`/`getInitialProps`/a Route Handler/React Server Component; (b) the store initializer (or an auto-fetch it triggers) runs a `set` at module load on the server; (c) a callback passed into server-rendered JSX calls an action that mutates during render; (d) any code path that calls `useStore.setState(...)` or the `set` arg outside of `useEffect`/event handlers while on the server.","commonSituations":"Migrating a CRA/Vite app to Next.js/Remix and forgetting that store actions now run on the server; an RTK/zustand-style 'fetch on init' pattern that calls `set` inside the creator; React 18 server components calling client store setters; a shared singleton store mutated during `getServerSideProps` to pass data to the page (should be props instead); toggling `isSSR` incorrectly or relying on a stale `typeof window` check after a framework upgrade that changed SSR timing; effects that are not actually effect-gated and run during server render in strict mode.","solutions":["Move the `set(...)` call out of the server code path: put it inside `useEffect` (effects do not run during SSR) or inside an event handler / user action.","Stop mutating the store to pass server-fetched data; instead return the data from `getServerSideProps`/loader as props and seed the store via its initial state in the creator (`create(initialState)`), not via a post-creation `setState`.","Gate the mutation explicitly: wrap the call in `if (typeof window !== 'undefined') { ... }` or pass `isSSR={false}` only when you have confirmed a client context.","If the mutation is a legitimate one-time initialization, perform it in the store creator's body before returning state, or use `createJSONStorage`/hydration APIs rather than calling `set` after creation.","Search the stack trace for the first `set`/`setState` call and trace it back to its caller; the offending caller is the one running during SSR — relocate it.","If SSR mutation is genuinely desired (e.g. seeding per-request state), do not use `ssrSafe` for that store; use a per-request store instance or `createStore` scoped to the request instead."],"exampleFix":"// before — action runs during SSR (e.g. called from getServerSideProps)\nexport const useUser = create<UserState>()(\n  unstable_ssrSafe((set) => ({\n    user: null,\n    loadUser: async (id) => {\n      const u = await fetchUser(id)\n      set({ user: u }) // throws: 'Cannot set state of Zustand store in SSR'\n    },\n  })),\n)\n\n// after — seed from server props, fetch only on the client\nexport const useUser = create<UserState>()(\n  unstable_ssrSafe((set, get) => ({\n    user: null,\n    hydrate: (u) => set({ user: u }), // called from a Client Component with SSR props\n    loadUser: async (id) => {\n      if (typeof window === 'undefined') return\n      const u = await fetchUser(id)\n      set({ user: u })\n    },\n  })),\n)\n// page: getServerSideProps returns { props: { initialUser } },\n// and a <ClientUser initialUser={initialUser} /> calls hydrate(initialUser) in useEffect.","handlingStrategy":"validation","validationCode":"// Run before calling any store action that may mutate.\n// Returns true when it is safe to call set/setState under ssrSafe.\nfunction canMutateStore(isSSRFlag = typeof window === 'undefined'): boolean {\n  return !isSSRFlag\n}\n\n// usage\nif (canMutateStore()) {\n  useUser.getState().loadUser(id)\n}","typeGuard":"// Narrowing helper: only call the mutating branch inside the guarded callback.\nfunction isClient(): boolean {\n  return typeof window !== 'undefined'\n}\n\n// usage in a component\nuseEffect(() => {\n  if (!isClient()) return\n  loadUser(id)\n}, [id])","tryCatchPattern":"// Not recommended: this error signals a real SSR design bug, so catching it\n// hides the symptom. Prefer validation (gate with typeof window). If you must\n// isolate a third-party call, catch narrowly and rethrow non-SSR errors.\ntry {\n  store.setState({ user })\n} catch (err) {\n  if (err instanceof Error && /SSR/.test(err.message)) {\n    // expected during SSR; defer to client\n    return\n  }\n  throw err\n}","preventionTips":["Never call store setters from `getServerSideProps`, `getStaticProps`, route handlers, or React Server Components — pass data as props and hydrate on the client.","Default every mutation to live inside `useEffect` or an event handler; only the store creator should set initial state at construction time.","Keep an `isClient()` helper (`typeof window !== 'undefined'`) and use it as the single source of truth for client-only branches.","Per-request SSR data belongs in a scoped store instance or in framework state/props, never in a module singleton mutated on the server.","When adopting `unstable_ssrSafe`, run a server-only smoke test (e.g. render the page to string in Node) so any SSR mutation fails in CI, not in production.","Pass the `isSSR` argument explicitly instead of relying on the default `typeof window` check when your framework customizes the SSR signal."],"tags":["zustand","ssr","hydration","react","nextjs","middleware","state-management"],"backgroundTag":null,"analyzedSha":"beca84e600e4e250f6b244d22878e72948f331c7","analyzedAt":"2026-08-12T13:27:03.031Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}