//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Use o middleware persist para salvar e restaurar automaticamente o estado do store no localStorage, sessionStorage ou qualquer motor de armazenamento personalizado.
import { create } from "zustand";
import { persist } from "zustand/middleware";
interface PreferencesStore {
language: string;
currency: string;
setLanguage: (lang: string) => void;
setCurrency: (currency: string) => void;
}
export const usePreferencesStore = create<PreferencesStore>()(
persist(
(set) => ({
language: "en",
currency: "USD",
setLanguage: (language) => set({ language }),
setCurrency: (currency) => set({ currency }),
}),
{
name: "user-preferences", // chave do localStorage
}
)
);// stores/app-store.ts
import { create } from "zustand";
import { persist, createJSONStorage, StateStorage } from "zustand/middleware";
interface AppState {
user: { name: string; email: string } | null;
theme: "light" | "dark";
recentSearches: string[];
setUser: (user: AppState["user"]) => void;
setTheme: (theme: AppState["theme"]) => void;
addSearch: (query: string) => void;
clearSearches: () => void;
}
// Motor de armazenamento personalizado via IndexedDB com idb-keyval
const indexedDBStorage: StateStorage = {
getItem: async (name) => {
const { get } = await import("idb-keyval");
return (await get(name)) || null;
},
setItem: async (name, value) => {
const { set } = await import("idb-keyval");
await set(name, value);
},
removeItem: async (name) => {
const { del } = await import("idb-keyval");
await del(name);
},
};
export const useAppStore = create<AppState>()(
persist(
(set) => ({
user: null,
theme: "light",
recentSearches: [],
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
addSearch: (query) =>
set((state) => ({
recentSearches: [
query,
...state.recentSearches.filter((s) => s !== query),
].slice(0, 10),
})),
clearSearches: () => set({ recentSearches: [] }),
}),
{
name: "app-storage",
storage: createJSONStorage(() => indexedDBStorage),
partialize: (state) => ({
theme: state.theme,
recentSearches: state.recentSearches,
// Excluir usuário - buscar novamente da API no carregamento
}),
version: 2,
migrate: (persistedState: any, version) => {
if (version === 0) {
// v0 tinha "darkMode: boolean" em vez de "theme"
persistedState.theme = persistedState.darkMode ? "dark" : "light";
delete persistedState.darkMode;
}
if (version < 2) {
// v1 tinha "searches" em vez de "recentSearches"
persistedState.recentSearches = persistedState.searches || [];
delete persistedState.searches;
}
return persistedState;
},
}
)
);// components/hydration-gate.tsx
"use client";
import { useEffect, useState } from "react";
import { useAppStore } from "@/stores/app-store";
export function HydrationGate({ children }: { children: React.ReactNode }) {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
// Esperar pela reidratação do persist
const unsub = useAppStore.persist.onFinishHydration(() => {
setHydrated(true);
});
// Se já hidratado
if (useAppStore.persist.hasHydrated()) {
setHydrated(true);
}
return unsub;
}, []);
if (!hydrated) return <div>Carregando...</div>;
return <>{children}</>;
}persist intercepta cada chamada set e escreve o novo estado no armazenamento após a atualização do estado.persist lê do armazenamento e mescla o estado persistido com o estado padrão.partialize controla quais propriedades do estado são salvas. Por padrão, todo o estado (incluindo funções) é serializado.version e migrate permitem a evolução do esquema. Quando a versão armazenada é menor que a versão atual, migrate transforma o estado antigo.createJSONStorage envolve uma interface StateStorage com serialização e desserialização JSON.sessionStorage:
import { createJSONStorage } from "zustand/middleware";
persist(storeCreator, {
name: "session-store",
storage: createJSONStorage(() => sessionStorage),
});Persistência seletiva com merge:
persist(storeCreator, {
name: "store",
partialize: (state) => ({ theme: state.theme, lang: state.lang }),
merge: (persistedState, currentState) => ({
...currentState,
...(persistedState as Partial<State>),
}),
});Armazenamento criptografado:
const encryptedStorage: StateStorage = {
getItem: (name) => {
const raw = localStorage.getItem(name);
if (!raw) return null;
return decrypt(raw);
},
setItem: (name, value) => {
localStorage.setItem(name, encrypt(value));
},
removeItem: (name) => {
localStorage.removeItem(name);
},
};Limpar dados persistidos:
// Limpar programaticamente o estado persistido
useAppStore.persist.clearStorage();
// Ou manualmente
localStorage.removeItem("app-storage");persist preserva a assinatura de tipo do store.partialize deve retornar um tipo parcial seguro. Use Pick<State, keys> para tipagem explícita.migrate recebe unknown para o estado persistido. Faça o cast com cuidado.persist<AppState>(storeCreator, {
name: "store",
partialize: (state): Pick<AppState, "theme" | "language"> => ({
theme: state.theme,
language: state.language,
}),
migrate: (persisted: unknown, version: number) => {
const state = persisted as Partial<AppState>;
return state as AppState;
},
});onFinishHydration ou hasHydrated() para controlar a renderização.null por JSON.stringify. Sempre use partialize para excluir ações, ou elas serão perdidas e substituídas pelos padrões na reidratação.version tem o valor padrão 0. Se você não definir uma versão e precisar de migrações posteriormente, deve começar da versão 0 em sua função migrate.storage para sincronização entre abas.| Abordagem | Prós | Contras |
|---|---|---|
| localStorage (padrão) | Leitura simples e síncrona | Limite de 5MB, bloqueia a thread principal |
| sessionStorage | Limpa automaticamente ao fechar a aba | Não compartilhado entre abas |
| IndexedDB | Grande capacidade, assíncrono | Configuração mais complexa |
| Armazenamento de Cookie | Disponível no servidor (SSR) | Limite de 4KB, enviado com cada requisição |
import { persist } from "zustand/middleware";
create<MyState>()(
persist(storeCreator, { name: "storage-key" })
);name (a chave do localStorage) é necessária.onFinishHydration ou hasHydrated() para controlar a renderização até que a hidratação seja concluída.partialize controla quais propriedades do estado são salvas no armazenamento.version na configuração do persist.migrate que transforma o estado antigo para a nova forma.migrate é executada quando a versão armazenada é menor que a versão atual.StateStorage (getItem, setItem, removeItem).createJSONStorage(() => yourStorage).storage para sincronização entre abas.useAppStore.persist.clearStorage();
// Ou manualmente:
localStorage.removeItem("app-storage");useAppStore.persist.onFinishHydration() em um useEffect para definir um sinalizador hydrated.useAppStore.persist.hasHydrated() para verificações síncronas.persist<AppState>(storeCreator, {
name: "store",
partialize: (state): Pick<AppState, "theme" | "language"> => ({
theme: state.theme,
language: state.language,
}),
});migrate recebe unknown para o estado persistido e um number para a versão.const state = persisted as Partial<AppState>.createJSONStorage(() => sessionStorage) como a opção storage.Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥