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#
- Set up the API route: Create a streaming endpoint that returns a ReadableStream
- Configure headers: Set appropriate SSE headers for streaming
- Implement client consumer: Use the Fetch API's
body.getReader()or the AI SDK hooks - Handle loading states: Show typing indicators while streaming
- Process incremental updates: Update UI as chunks arrive
Best Practices#
- Use the AI SDK when possible - The
useChathook handles edge cases like reconnection and error handling - Show loading indicators - Display a typing indicator while waiting for the first chunk
- Handle errors gracefully - Implement error boundaries and retry logic for failed streams
- Consider connection limits - Be aware of browser connection limits when opening multiple streams
- Implement abort signals - Allow users to cancel long-running requests
- Buffer partial tokens - Some streaming implementations may split tokens; buffer appropriately
- Test on slow connections - Verify behavior on throttled networks
Related Patterns#
- Claude API Integration - Basic Claude API setup
- Function Calling - AI tool use with streaming
- RAG - Streaming with retrieval augmented generation