Memory for Vercel AI SDK Applications

The Vercel AI SDK makes streaming chat UIs effortless, but conversation memory vanishes between sessions. This guide adds REM Labs as a persistent memory backend to any Next.js AI app -- store user context, retrieve it with multi-signal search, and ship a chatbot that actually remembers.

The Problem: Stateless Streams

The Vercel AI SDK's useChat hook keeps messages in React state. Refresh the page, deploy a new version, or switch devices -- the conversation is gone. For production apps, you need server-side memory that persists and scales.

Step 1: Install

npm install @remlabs/memory ai @ai-sdk/openai

The @remlabs/memory package is the official Node.js SDK. Get an API key free at remlabs.ai/console.

Step 2: Server-Side Route with Memory

// app/api/chat/route.ts import { openai } from "@ai-sdk/openai"; import { streamText } from "ai"; import { RemMemory } from "@remlabs/memory"; const mem = new RemMemory({ apiKey: process.env.REM_API_KEY }); export async function POST(req: Request) { const { messages, userId } = await req.json(); const lastMessage = messages[messages.length - 1].content; // Retrieve relevant memories for this user const memories = await mem.search(lastMessage, { namespace: `user-${userId}`, limit: 5 }); const context = memories.map(m => m.value).join("\n"); const result = streamText({ model: openai("gpt-4o"), system: `You are a helpful assistant. Relevant context from previous conversations:\n${context}`, messages }); // Store the latest exchange after streaming mem.store({ value: lastMessage, namespace: `user-${userId}`, tags: ["conversation"] }); return result.toDataStreamResponse(); }

Before each response, the route searches REM for memories relevant to the user's latest message. After responding, it stores the new message. The user gets personalized answers that improve over time.

Step 3: Client-Side Hook

// app/page.tsx "use client"; import { useChat } from "ai/react"; export default function Chat() { const { messages, input, handleInputChange, handleSubmit } = useChat({ body: { userId: "user-123" } }); return ( <div> {messages.map(m => ( <div key={m.id}>{m.role}: {m.content}</div> ))} <form onSubmit={handleSubmit}> <input value={input} onChange={handleInputChange} /> </form> </div> ); }

Nothing special on the client -- the standard useChat hook works as-is. Memory logic is entirely server-side, so it works with Edge Runtime, serverless functions, or a long-running Node server.

Step 4: Query Memories from an API Route

// app/api/memories/route.ts import { RemMemory } from "@remlabs/memory"; const mem = new RemMemory({ apiKey: process.env.REM_API_KEY }); export async function GET(req: Request) { const { searchParams } = new URL(req.url); const userId = searchParams.get("userId"); const memories = await mem.search("user preferences", { namespace: `user-${userId}`, limit: 10 }); return Response.json({ memories }); }

Why Not Just Use a Database?

You could store messages in Postgres. But retrieving relevant context from thousands of past messages requires more than SQL queries. REM indexes every memory three ways -- vector embeddings for semantic similarity, full-text for exact keyword matching, and entity graphs for structured lookups. Multi-signal fusion retrieval scores 90% on LongMemEval. A Postgres LIKE query does not come close.

Edge-compatible: The @remlabs/memory SDK uses standard fetch internally, so it works in Vercel Edge Runtime, Cloudflare Workers, and any environment that supports the Web Fetch API.

Give your AI app a memory

Free tier. No credit card. npm install and go.

Get started free →