# AI SDK UI - Streaming Best Practices UI patterns and best practices for streaming AI responses. **Last Updated**: 2025-10-22 --- ## Performance ### Always Use Streaming for Long-Form Content ```tsx // ✅ GOOD: Streaming provides better perceived performance const { messages } = useChat({ api: '/api/chat' }); // ❌ BAD: Blocking - user waits for entire response const response = await fetch('/api/chat', { method: 'POST' }); ``` **Why?** - Users see tokens as they arrive - Perceived performance is much faster - Users can start reading before response completes - Can stop generation early --- ## UX Patterns ### 1. Show Loading States ```tsx const { messages, isLoading } = useChat(); {isLoading && (
)} ``` ### 2. Provide Stop Button ```tsx const { isLoading, stop } = useChat(); {isLoading && ( )} ``` ### 3. Auto-Scroll to Latest Message ```tsx const messagesEndRef = useRef(null); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
``` ### 4. Disable Input While Loading ```tsx setInput(e.target.value)} disabled={isLoading} // Prevent new messages while generating className="disabled:bg-gray-100" /> ``` ### 5. Handle Empty States ```tsx {messages.length === 0 ? (

Start a conversation

Ask me anything!

) : ( // Messages list )} ``` --- ## Error Handling ### 1. Display Errors to Users ```tsx const { error } = useChat(); {error && (
Error: {error.message}
)} ``` ### 2. Provide Retry Functionality ```tsx const { error, reload } = useChat(); {error && (
{error.message}
)} ``` ### 3. Handle Network Failures Gracefully ```tsx useChat({ onError: (error) => { console.error('Chat error:', error); // Log to monitoring service (Sentry, etc.) // Show user-friendly message }, }); ``` ### 4. Log Errors for Debugging ```tsx useChat({ onError: (error) => { const errorLog = { timestamp: new Date().toISOString(), message: error.message, url: window.location.href, }; console.error('AI SDK Error:', errorLog); // Send to Sentry/Datadog/etc. }, }); ``` --- ## Message Rendering ### 1. Support Markdown Use `react-markdown` for rich content: ```tsx import ReactMarkdown from 'react-markdown'; {messages.map(m => ( {m.content} ))} ``` ### 2. Handle Code Blocks ```tsx import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; {String(children)} ) : ( {children} ); }, }} > {message.content} ``` ### 3. Display Tool Calls Visually ```tsx {message.toolInvocations?.map((tool, idx) => (
Tool: {tool.toolName}
Args: {JSON.stringify(tool.args)}
{tool.result && (
Result: {JSON.stringify(tool.result)}
)}
))} ``` ### 4. Show Timestamps ```tsx
{new Date(message.createdAt).toLocaleTimeString()}
``` ### 5. Group Messages by Role ```tsx {messages.reduce((groups, message, idx) => { const prevMessage = messages[idx - 1]; const showRole = !prevMessage || prevMessage.role !== message.role; return [ ...groups,
{showRole &&
{message.role}
}
{message.content}
]; }, [])} ``` --- ## State Management ### 1. Persist Chat History ```tsx const chatId = 'chat-123'; const { messages } = useChat({ id: chatId, initialMessages: loadFromLocalStorage(chatId), }); useEffect(() => { saveToLocalStorage(chatId, messages); }, [messages, chatId]); ``` ### 2. Clear Chat Functionality ```tsx const { setMessages } = useChat(); const clearChat = () => { if (confirm('Clear chat history?')) { setMessages([]); } }; ``` ### 3. Export/Import Conversations ```tsx const exportChat = () => { const json = JSON.stringify(messages, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chat-${Date.now()}.json`; a.click(); }; const importChat = (file: File) => { const reader = new FileReader(); reader.onload = (e) => { const imported = JSON.parse(e.target?.result as string); setMessages(imported); }; reader.readAsText(file); }; ``` ### 4. Handle Multiple Chats (Routing) ```tsx // Use URL params for chat ID const searchParams = useSearchParams(); const chatId = searchParams.get('chatId') || 'default'; const { messages } = useChat({ id: chatId, initialMessages: loadMessages(chatId), }); // Navigation New Chat ``` --- ## Advanced Patterns ### 1. Debounced Input for Completions ```tsx import { useDebouncedCallback } from 'use-debounce'; const { complete } = useCompletion(); const debouncedComplete = useDebouncedCallback((value) => { complete(value); }, 500); debouncedComplete(e.target.value)} /> ``` ### 2. Optimistic Updates ```tsx const { messages, sendMessage } = useChat(); const optimisticSend = (content: string) => { // Add user message immediately const tempMessage = { id: `temp-${Date.now()}`, role: 'user', content, }; setMessages([...messages, tempMessage]); // Send to server sendMessage({ content }); }; ``` ### 3. Custom Message Formatting ```tsx const formatMessage = (content: string) => { // Replace @mentions content = content.replace(/@(\w+)/g, '@$1'); // Replace URLs content = content.replace( /(https?:\/\/[^\s]+)/g, '$1' ); return content; };
``` ### 4. Typing Indicators ```tsx const [isTyping, setIsTyping] = useState(false); useChat({ onFinish: () => setIsTyping(false), }); const handleSend = (content: string) => { setIsTyping(true); sendMessage({ content }); }; {isTyping &&
AI is typing...
} ``` --- ## Performance Optimization ### 1. Virtualize Long Message Lists ```tsx import { FixedSizeList } from 'react-window'; {({ index, style }) => (
{messages[index].content}
)}
``` ### 2. Lazy Load Message History ```tsx const [page, setPage] = useState(1); const messagesPerPage = 50; const visibleMessages = messages.slice( (page - 1) * messagesPerPage, page * messagesPerPage ); ``` ### 3. Memoize Message Rendering ```tsx import { memo } from 'react'; const MessageComponent = memo(({ message }: { message: Message }) => { return
{message.content}
; }); {messages.map(m => )} ``` --- ## Official Documentation - **AI SDK UI Overview**: https://ai-sdk.dev/docs/ai-sdk-ui/overview - **Streaming Protocols**: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocols - **Message Metadata**: https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata --- **Last Updated**: 2025-10-22