Persisting Zustand State with the persist Middleware: A Practical Guide
Learn how Zustand’s persist middleware automatically saves store state to localStorage and rehydrates it on page reload, with a working counter example and practical verification steps.
07 Oct 2025, 20:45 UTC

Problem: Losing UI state on page reload
When you build a React app with Zustand, any state you keep in a store disappears as soon as the user refreshes the page. This forces you to re‑fetch data or reset UI controls, which hurts the user experience and adds unnecessary boilerplate.
Thesis: Zustand’s persist middleware automatically syncs store state with browser storage and rehydrates it on startup, removing the need for manual save/load logic.
How persist works
The persist middleware wraps a Zustand store. You give it a configuration object that specifies:
name– the key under which the state is stored inlocalStorageorsessionStorage.storage– the storage engine (default islocalStorage).- Optional
versionandmigratefunctions for schema evolution.
When the store is created, persist reads the stored JSON (if any), merges it with the store’s initial state, and then keeps the storage in sync: every state change triggers a write, and every page load triggers a read (hydration).
Worked example: a counter that survives reloads
First, install Zustand in a React project (run in your project root):
npm install zustand
Then create a store with persist:
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useCounterStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}),
{
name: 'counter-storage', // key in localStorage
storage: window.localStorage // explicit, but default
}
)
)
export default useCounterStore
Use the store in a component:
import useCounterStore from './useCounterStore'
function Counter() {
const { count, increment } = useCounterStore()
return (
Count: {count}
+1
)
}
When you click the button, the count updates and the middleware writes the new state to localStorage under the key counter-storage. Reload the page: the store reads that JSON, merges it with the initial state ({ count: 0 }), and the component renders the persisted value.
Trade‑off and limitation
Persist only works with serializable state. If you store functions, class instances, or circular references, the default JSON serialization will throw an error. You can supply a custom serialize/deserialize pair, but that adds complexity.
Because the middleware writes to storage on every state change, high‑frequency updates (e.g., mouse‑move coordinates) can cause excessive I/O and degrade performance. In such cases, consider debouncing writes or storing only aggregated data.
How to verify persistence works
- Open your app in a browser, update the counter a few times.
- Open DevTools → Application → Local Storage → (your origin).
- Find the key
counter-storage; its value should be a JSON string like{"count":3}. - Reload the page and confirm the displayed count matches the stored value.
- To confirm the middleware’s effect, temporarily remove the
persistwrapper, reload, and observe that the count resets to the initial value.
These steps require only standard browser developer tools; no special permissions are needed beyond the ability to read/write localStorage for the origin.
Actionable closing
If you need UI state to survive refreshes without writing custom save/load code, add Zustand’s persist to your store. Keep the state serializable, watch for write frequency, and verify persistence via the Application panel in DevTools. This small change eliminates a common source of user‑facing friction and lets you focus on the core logic of your application.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.