AI Streaming Pattern

Implement real-time AI response streaming for chat interfaces and content generation with smooth user experience.

Overview#

Streaming AI responses provides immediate feedback to users, creating a more engaging experience similar to how humans communicate. Instead of waiting for a complete response, users see text appear incrementally as the AI generates it.

When to use:

  • Chat interfaces and conversational UIs
  • Long-form content generation
  • Real-time response display
  • Any AI interaction where perceived latency matters

Key features:

  • Incremental response display
  • Server-Sent Events (SSE) support
  • Native Anthropic streaming
  • Vercel AI SDK integration
  • Client-side stream consumption

Code Example#

Anthropic Native Streaming#

1// app/api/chat/route.ts 2import { anthropic } from '@/lib/anthropic' 3 4export async function POST(req: Request) { 5 const { messages } = await req.json() 6 7 const stream = await anthropic.messages.stream({ 8 model: 'claude-sonnet-4-20250514', 9 max_tokens: 1024, 10 messages 11 }) 12 13 return new Response(stream.toReadableStream(), { 14 headers: { 15 'Content-Type': 'text/event-stream', 16 'Cache-Control': 'no-cache', 17 Connection: 'keep-alive' 18 } 19 }) 20}

Vercel AI SDK Streaming#

1// app/api/chat/route.ts 2import { anthropic } from '@ai-sdk/anthropic' 3import { streamText } from 'ai' 4 5export async function POST(req: Request) { 6 const { messages } = await req.json() 7 8 const result = streamText({ 9 model: anthropic('claude-sonnet-4-20250514'), 10 messages 11 }) 12 13 return result.toDataStreamResponse() 14}

Client-Side Stream Consumer#

1// components/chat.tsx 2'use client' 3import { useState } from 'react' 4 5interface Message { 6 role: 'user' | 'assistant' 7 content: string 8} 9 10export function Chat() { 11 const [messages, setMessages] = useState<Message[]>([]) 12 const [input, setInput] = useState('') 13 const [isLoading, setIsLoading] = useState(false) 14 15 async function handleSubmit(e: React.FormEvent) { 16 e.preventDefault() 17 if (!input.trim()) return 18 19 const userMessage = { role: 'user' as const, content: input } 20 setMessages(prev => [...prev, userMessage]) 21 setInput('') 22 setIsLoading(true) 23 24 const response = await fetch('/api/chat', { 25 method: 'POST', 26 body: JSON.stringify({ 27 messages: [...messages, userMessage] 28 }) 29 }) 30 31 const reader = response.body?.getReader() 32 const decoder = new TextDecoder() 33 let assistantMessage = '' 34 35 // Add placeholder for assistant message 36 setMessages(prev => [...prev, { role: 'assistant', content: '' }]) 37 38 while (reader) { 39 const { done, value } = await reader.read() 40 if (done) break 41 42 assistantMessage += decoder.decode(value) 43 setMessages(prev => [ 44 ...prev.slice(0, -1), 45 { role: 'assistant', content: assistantMessage } 46 ]) 47 } 48 49 setIsLoading(false) 50 } 51 52 return ( 53 <div className="flex flex-col h-full"> 54 <div className="flex-1 overflow-y-auto p-4 space-y-4"> 55 {messages.map((m, i) => ( 56 <div 57 key={i} 58 className={`p-4 rounded-lg ${ 59 m.role === 'user' ? 'bg-blue-100 ml-12' : 'bg-gray-100 mr-12' 60 }`} 61 > 62 {m.content} 63 </div> 64 ))} 65 </div> 66 <form onSubmit={handleSubmit} className="p-4 border-t"> 67 <input 68 value={input} 69 onChange={(e) => setInput(e.target.value)} 70 placeholder="Type a message..." 71 className="w-full p-2 border rounded" 72 disabled={isLoading} 73 /> 74 </form> 75 </div> 76 ) 77}

AI SDK useChat Hook#

The Vercel AI SDK provides a convenient useChat hook that handles streaming automatically:

1// components/chat.tsx 2'use client' 3import { useChat } from 'ai/react' 4 5export function Chat() { 6 const { messages, input, handleInputChange, handleSubmit, isLoading } = 7 useChat() 8 9 return ( 10 <div className="flex flex-col h-full"> 11 <div className="flex-1 overflow-y-auto p-4 space-y-4"> 12 {messages.map((m) => ( 13 <div 14 key={m.id} 15 className={`p-4 rounded-lg ${ 16 m.role === 'user' ? 'bg-blue-100 ml-12' : 'bg-gray-100 mr-12' 17 }`} 18 > 19 {m.content} 20 </div> 21 ))} 22 </div> 23 <form onSubmit={handleSubmit} className="p-4 border-t"> 24 <input 25 value={input} 26 onChange={handleInputChange} 27 placeholder="Type a message..." 28 className="w-full p-2 border rounded" 29 disabled={isLoading} 30 /> 31 </form> 32 </div> 33 ) 34}

Usage Instructions#

  1. Set up the API route: Create a streaming endpoint that returns a ReadableStream
  2. Configure headers: Set appropriate SSE headers for streaming
  3. Implement client consumer: Use the Fetch API's body.getReader() or the AI SDK hooks
  4. Handle loading states: Show typing indicators while streaming
  5. Process incremental updates: Update UI as chunks arrive

Best Practices#

  1. Use the AI SDK when possible - The useChat hook handles edge cases like reconnection and error handling
  2. Show loading indicators - Display a typing indicator while waiting for the first chunk
  3. Handle errors gracefully - Implement error boundaries and retry logic for failed streams
  4. Consider connection limits - Be aware of browser connection limits when opening multiple streams
  5. Implement abort signals - Allow users to cancel long-running requests
  6. Buffer partial tokens - Some streaming implementations may split tokens; buffer appropriately
  7. Test on slow connections - Verify behavior on throttled networks