Implementing Persistent State in Zustand with the Persist Middleware
Learn how to use Zustand's persist middleware to synchronize state with localStorage, handle SSR hydration mismatches, and secure sensitive data using partialization.
02 Nov 2025, 11:11 UTC

Solving State Loss on Page Refresh
Single Page Applications (SPAs) lose their in-memory state whenever a user refreshes the browser or navigates away. While global state managers like Zustand handle data flow efficiently, they do not natively save data to the disk. To prevent users from losing form progress, authentication tokens, or UI preferences, you must synchronize the store with a browser storage engine.
The persist middleware automates this by intercepting state changes and writing them to localStorage or sessionStorage, then reloading that data during the application's initialization phase (hydration).
Prerequisites
- A React project with
zustandinstalled (v4.0.0 or later). - A defined state structure that does not contain non-serializable data (such as class instances or functions), as these cannot be stored as JSON.
Configuring the Persistent Store
To enable persistence, wrap your store definition in the persist middleware. You must provide a unique name, which serves as the key in the browser's storage.
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface UserSettings {
theme: 'light' | 'dark';
fontSize: number;
sessionToken: string | null;
updateTheme: (theme: 'light' | 'dark') => void;
updateFontSize: (size: number) => void;
}
export const useSettingsStore = create<UserSettings>()(
persist(
(set) => ({
theme: 'light',
fontSize: 14,
sessionToken: null,
updateTheme: (theme) => set({ theme }),
updateFontSize: (size) => set({ fontSize: size }),
}),
{
name: 'user-settings-storage',
// Use localStorage by default, or switch to sessionStorage
storage: createJSONStorage(() => localStorage),
// partialize allows you to pick only specific fields to save
partialize: (state) => ({
theme: state.theme,
fontSize: state.fontSize,
}),
}
)
);
Engineering Decisions: Partialization and Security
A common mistake is persisting the entire store. This can lead to two primary issues: storage limit exhaustion (localStorage is typically capped at 5MB) and security vulnerabilities.
Partialization is the process of filtering the state. In the example above, the sessionToken is intentionally omitted from the partialize object. This ensures that sensitive authentication tokens are not stored in plaintext in the browser's local storage, where they are vulnerable to Cross-Site Scripting (XSS) attacks.
Handling Hydration in SSR
In Server-Side Rendering (SSR) frameworks like Next.js, the server renders the HTML before the client-side JavaScript executes. Because the server has no access to localStorage, the initial render will use the default state, while the client will immediately update to the persisted state. This causes a Hydration Mismatch error.
To resolve this, implement a hydration check to ensure the component only renders persisted data after the client has mounted:
import { useState, useEffect } from 'react';
import { useSettingsStore } from './store';
export function ThemeDisplay() {
const theme = useSettingsStore((state) => state.theme);
const [hasHydrated, setHasHydrated] = useState(false);
useEffect(() => {
setHasHydrated(true);
}, []);
if (!hasHydrated) {
return <div>Loading...</div>;
}
return <p>Current Theme: {theme}</p>;
}
Verification and Diagnostics
To verify the implementation is working correctly, follow these steps:
- Trigger a State Change: Call an update function (e.g.,
updateTheme('dark')) within your app. - Inspect Storage: Open Browser DevTools > Application Tab > Local Storage. Look for the key
user-settings-storage. The value should be a JSON string containing the current state. - Test Persistence: Refresh the page. The UI should immediately reflect the 'dark' theme without requiring a new user action.
Rollback and State Reset
If the persisted state becomes corrupted or you need to clear it during development, you can manually remove the key from storage. This operation changes the browser's state and will revert the store to its initial defaults on the next load.
Run this command in the browser console to clear the specific store:
localStorage.removeItem('user-settings-storage');
window.location.reload();0 replies
A thoughtful contribution can make all the difference. Be the first to share one.