Careeroza
Streaming Frontend with Next.js
Reading the Stream in React
EventSource only supports GET, so for a POST chat request read the response body stream with fetch and split it into SSE events. A minimal client component for a Next.js app (the same code works in plain React):
'use client';
import { useState } from 'react';
type Msg = { role: 'user' | 'assistant'; text: string };
export default function Chat() {
const [messages, setMessages] = useState<Msg[]>([]);
const [input, setInput] = useState('');
const [busy, setBusy] = useState(false);
async function send() {
const text = input.trim();
if (!text || busy) return;
setInput('');
setBusy(true);
setMessages((m) => [...m, { role: 'user', text }, { role: 'assistant', text: '' }]);
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conversation_id: 'c-77', message: text }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop() ?? ''; // keep an incomplete event for the next chunk
for (const e of events) {
if (!e.startsWith('data: ')) continue;
const data = JSON.parse(e.slice(6));
if (data.type === 'text' || data.type === 'error') {
const piece = data.type === 'text' ? data.text : data.message;
setMessages((m) => {
const copy = [...m];
copy[copy.length - 1] = { role: 'assistant', text: copy[copy.length - 1].text + piece };
return copy;
});
}
}
}
setBusy(false);
}
return (
<div className="chat">
{messages.map((m, i) => (
<p key={i} className={m.role}>{m.text}</p>
))}
<input value={input} onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && send()} placeholder="Ask ShopEase..." />
<button onClick={send} disabled={busy}>Send</button>
</div>
);
}Wiring and UX Tips
- In development, proxy
/api/chatto FastAPI with Next.jsrewritesinnext.config.js, or call the backend URL directly with CORS enabled. - Never call the LLM API from the browser: the API key would be visible to everyone.
- Show a typing indicator until the first event arrives, disable Send while streaming, and render Markdown only after sanitizing it.
- Alternative: write the route in Next.js itself (a Route Handler using
@anthropic-ai/sdk) if your backend is Node.