Session Management in AI Chat Applications
aichatsessionsreactnextjsux6 min read

Session Management in AI Chat Applications

Admin

AI chat apps feel simple on the surface: a prompt box, a response stream, maybe some citations. Underneath, session management gets complicated fast:

  • How do you persist conversation history?
  • How much context do you send to the model?
  • What happens when users refresh, switch devices, or lose connectivity?
  • How do you keep the UI fast and accessible while handling all of this?

Good session management makes your AI app feel like a reliable collaborator, not a forgetful toy. The AI UX Kit includes patterns and hooks that help you get this right from day one.

🧠 What “Session” Means in AI Chat

A “session” is more than a single request/response:

  • Conversation history: user + assistant messages across multiple turns.
  • Context window: what subset of that history you send to the model.
  • Metadata: user ID, model version, feature flags, experiments.
  • Persistence: how long sessions live, how they’re resumed, and where they’re stored.

Your UI needs to:

🧩 Core UI Pattern: Session Chat Hook

The AI UX Kit ships with session-aware chat hooks (e.g. use-session-chat) that centralize:

  • Message state
  • Loading and error states
  • Basic persistence strategy

Here’s what a simplified usage pattern looks like:

import { useSessionChat } from "@/app/recipes/session/chat/hooks/use-session-chat"
import { PromptInput } from "@/components/ui/composites/prompt-input"
import { ResponseViewer } from "@/components/ui/composites/response-viewer"

export default function SessionChatPage() {
  const {
    messages,
    isLoading,
    error,
    sendMessage,
    retryLastMessage,
  } = useSessionChat({
    sessionId: "customer-support-session",
  })

  const handleSubmit = (value: string) => {
    sendMessage({ content: value })
  }

  return (
    <div className="flex flex-col h-screen">
      <div className="flex-1 overflow-auto p-6 space-y-4">
        {messages.map((message) => (
          <div key={message.id} className={message.role === "user" ? "text-right" : "text-left"}>
            {message.role === "assistant" ? (
              <ResponseViewer content={message.content} />
            ) : (
              <div className="inline-block bg-primary-50 rounded-lg px-4 py-2">
                {message.content}
              </div>
            )}
          </div>
        ))}

        {error && (
          <div className="mt-4 p-3 rounded-md bg-red-50 text-sm text-red-800 flex items-center justify-between">
            <span>{error.message}</span>
            <button
              type="button"
              onClick={retryLastMessage}
              className="ml-4 text-xs underline"
            >
              Retry
            </button>
          </div>
        )}
      </div>

      <div className="border-t p-4">
        <PromptInput
          onSubmit={(value) => handleSubmit(value)}
          disabled={isLoading}
          actionButton="send"
        />
      </div>
    </div>
  )
}

The heavy lifting (state management, pending states, basic retries) lives in the hook, not scattered across components.

🗄️ Where to Store Session Data

Session data can live in multiple places:

  • In-memory (React state): fast, but lost on refresh.
  • Local storage: persists across reloads, but only on one device.
  • Backend storage: durable and multi-device, but requires APIs.

The AI UX Kit is agnostic about where you store your data—it focuses on UI and UX—but its patterns make it easy to adapt.

Example: hydrating from persisted history:

const { messages, sendMessage, hydrateFromHistory } = useSessionChat({ sessionId })

React.useEffect(() => {
  const saved = window.localStorage.getItem(`chat:${sessionId}`)
  if (saved) {
    hydrateFromHistory(JSON.parse(saved))
  }
}, [sessionId, hydrateFromHistory])

React.useEffect(() => {
  window.localStorage.setItem(`chat:${sessionId}`, JSON.stringify(messages))
}, [sessionId, messages])

Now sessions survive a refresh without any UI changes.

📏 Managing Context Windows

LLM context windows force you to decide how much history to send with each prompt.

Common strategies:

  • Last N turns: send only the most recent messages (fast, simple).
  • Summarized history: compress older messages into a running summary.
  • Segmented sessions: split long conversations into chapters.

The UI needs to show the full history, even if the model only sees part of it.

Pseudocode for a last-N strategy:

function buildModelPayload(messages: ChatMessage[]) {
  const MAX_TURNS = 12
  const visibleHistory = messages.slice(-MAX_TURNS)

  return visibleHistory.map((m) => ({
    role: m.role,
    content: m.content,
  }))
}

You can implement this in your sendMessage handler, leaving the UI unchanged.

🔄 Handling Errors and Retries Gracefully

Network hiccups and model errors are inevitable. Bad UIs:

  • Lose the user’s message.
  • Show generic “Something went wrong” banners.
  • Offer no path to retry.

Better pattern (supported by AI UX Kit recipes):

  • Preserve the user’s message in the history.
  • Show per-message error states.
  • Allow “Retry” on that specific message.
// inside message rendering
{message.role === "assistant" && message.status === "error" && (
  <button
    type="button"
    onClick={() => retryMessage(message.id)}
    className="mt-2 text-xs text-red-700 underline"
  >
    Retry this response
  </button>
)}

The recipes in the kit encode patterns like error-handling chat, so you’re not starting from an empty file.

♿ Accessibility in Session UIs

Session management isn't just about data—it's also about how the experience feels:

  • Screen readers should announce new messages progressively.
  • Keyboard users should be able to:
    • Jump to the latest message,
    • Re-open prompts,
    • Interact with citations and feedback.

AI UX Kit's ResponseViewer and chat recipes are built with:

  • Proper aria-live regions for streaming content.
  • Focusable elements for citations and controls.
  • Predictable tab order across messages and input.

Example (simplified streaming component):

<ResponseViewer
  content={streamedContent}
  format="markdown"
  showCitations
  aria-live="polite"
  aria-atomic={false}
/>

You don’t have to become an ARIA expert to get a reasonable default.

🔐 Multi-Session Interfaces

Many apps need multiple concurrent sessions:

  • Conversations per project, customer, or case.
  • “Tabs” for different experiments.

Pattern:

  • Left sidebar lists sessions.
  • Right panel shows the active session chat.
  • All wired to the same session hook with different sessionIds.
function SessionsLayout({ sessionId }: { sessionId: string }) {
  const sessions = useUserSessions() // your data source

  return (
    <div className="flex h-screen">
      <aside className="w-72 border-r bg-secondary-50">
        {/* session list */}
        {sessions.map((session) => (
          <button
            key={session.id}
            className="w-full text-left px-4 py-2 hover:bg-secondary-100"
            onClick={() => router.push(`/sessions/${session.id}`)}
          >
            <div className="text-sm font-medium">{session.title}</div>
            <div className="text-xs text-secondary-600">
              Updated {session.updatedAtRelative}
            </div>
          </button>
        ))}
      </aside>

      <main className="flex-1">
        <SessionChatPage key={sessionId} />
      </main>
    </div>
  )
}

This mirrors the structure of the AI UX Kit's session recipes while letting you plug in your own persistence.

✅ Session Management Checklist

Before shipping your AI chat experience, check:

  • History

    • Users can see past messages after refresh.
    • Old messages don’t break layout on mobile.
    • Long histories still feel fast.
  • Context

    • You have a clear strategy for what goes to the model.
    • You don’t spam the model with the entire history on every call.
  • Resilience

    • Network errors preserve the user’s message.
    • Users can retry specific messages.
    • Timeouts/exceptions show clear, actionable errors.
  • Accessibility

    • New messages are announced to screen readers.
    • Keyboard users can navigate messages and actions.
    • Focus doesn’t jump unexpectedly during streaming.
  • Multi-session

    • Sessions have stable IDs and URLs.
    • Switching sessions is fast and predictable.
    • Session titles or metadata make sense to users.

🚀 How the AI UX Kit Helps

Instead of hand-rolling all of this, you can:

  • Use session chat recipes (/recipes/session/chat) as a starting point.
  • Use prompt input, response viewer, and feedback components that already respect accessibility and tokenized styling.
  • Adapt the included hooks (use-session-chat, use-error-chat, etc.) to your persistence and model APIs.

Explore the AI UX Kit: View components

With solid session management in place, your AI chat stops feeling like a toy—and starts feeling like a product users can trust. For more on building complete AI interfaces, see Production-Ready AI Interfaces with Next.js.

Related Posts