The Vercel AI SDK's useChat hook stores messages exclusively in React component state — a page refresh destroys the entire conversation with no recovery path. For any application where users build on prior context (debugging sessions, multi-step workflows, ongoing support threads), this is a reliability failure under iso-25010:2011: the system cannot maintain its service when subjected to a routine browser event. Users who lose history mid-workflow may lose irreplaceable problem context. Data-integrity also demands that committed user interactions not vanish silently; a conversation a user considers saved is, in effect, corrupted when it disappears.
Critical because in-memory-only storage guarantees total data loss on every page refresh, destroying user work with no recovery mechanism.
Persist messages to localStorage on every state change and load them as initialMessages on mount in src/components/chat/ChatWindow.tsx.
const { messages } = useChat({
initialMessages: JSON.parse(
localStorage.getItem('chat-history') || '[]'
),
});
useEffect(() => {
localStorage.setItem('chat-history', JSON.stringify(messages));
}, [messages]);
For authenticated users, replace localStorage with a database-backed route at app/api/chat/history/route.ts — write after each exchange and read on mount. localStorage is an acceptable fallback for anonymous sessions but must not be the only mechanism for logged-in users where history is a product guarantee.
ID: ai-chat-visibility.input-and-history.conversation-history-persisted
Severity: critical
What to look for: Enumerate all persistence mechanisms in the chat component: localStorage.setItem() calls, database writes on each message (via API routes), state management libraries (Zustand, Redux) with persistence middleware, or IndexedDB usage. Count how many of these are present. The Vercel AI SDK's useChat hook does NOT persist history by default — it is in-memory only. Check whether messages are loaded on component mount from a persistent source.
Pass criteria: Conversation messages are persisted to localStorage, sessionStorage, a database, or another durable store via at least 1 persistence mechanism. On page refresh, the conversation history is restored. Report even on pass: "History persisted via [mechanism] — N messages restored on mount."
Fail criteria: Conversation history exists only in React state (in-memory). Refreshing the page loses all history. No persistence mechanism is detected.
Do NOT pass when: Messages are saved only in a Zustand store without a persist middleware configured — in-memory-only Zustand is NOT a pass. Also do NOT pass when localStorage writes exist but initialMessages or equivalent mount-time loading is missing.
Skip (N/A) when: The interface is explicitly designed as ephemeral (e.g., a single-question widget, a kiosk mode, or a demo that intentionally resets each session). Signal: a visible "this conversation is not saved" notice, or product documentation describes the interface as ephemeral.
Detail on fail: "Conversation messages stored only in useChat in-memory state — no persistence to localStorage or database; all history lost on page refresh"
Remediation: In your chat component at src/components/chat/ChatWindow.tsx, add persistence:
const { messages } = useChat({
initialMessages: JSON.parse(localStorage.getItem('chat-history') || '[]'),
});
useEffect(() => {
localStorage.setItem('chat-history', JSON.stringify(messages));
}, [messages]);
For production applications with logged-in users, persist to a database via app/api/chat/history/route.ts after each exchange and load history on mount.
Cross-reference: For database-backed persistence patterns and data modeling, the Database Layer Audit covers schema design for conversation storage.