ChatClient keeps messages in memory only, so they vanish on reload. TanStack AI exposes a tiny persistence interface - getItem / setItem / removeItem - and any backend that implements it becomes durable storage.
Upstash Redis is a great fit: it’s serverless with a REST API (no connection pooling, works in any edge/serverless runtime), latency is low enough to write on every streamed token, and per-conversation keys with an optional TTL give you free expiry of stale chats.
This tutorial uses OpenAI for the model, but persistence is model-agnostic.
Prerequisites
- An Upstash Redis database
- A TanStack AI
ChatClient(@tanstack/ai-client) @upstash/redis
The adapter
A persistence adapter is just an object with three methods. Each may be sync or async — the client awaits them. We store the messages array under a namespaced key and revivecreatedAt (which becomes a string through JSON) on read.
Use it
Pass the adapter aspersistence and give the client a stable id — that id is the storage key, so the same id loads the same conversation back.
- Hydrates on construction — calls
getItem(id)and populates itself (overridinginitialMessages). - Saves on every change — calls
setItem(id, messages)on each new message and streamed chunk, through an ordered write queue. - Clears on
clear()— callsremoveItem(id).
Try it
Create a client, chat, then construct a brand-new client with the sameid — it hydrates the full history from Redis with no initialMessages:
Persistence is best-effort: TanStack AI swallows adapter errors so storage hiccups never break the chat. Handle errors inside the adapter if you need to react to them.
Next steps
- Check out Agent Memory with Redis Search for more advanced retrieval.
- Set
ttlSecondsto auto-expire idle conversations. - Namespace keys per user, e.g.
prefix: \chat:$:“. - Swap the same adapter shape onto any TanStack AI client (React/Vue/Solid/Svelte
useChat).