Streaming a chat UI
Render answers token-by-token for that instant feel. Never put your AIx key in the browser — proxy through a server route, then stream the bytes to the client.
1 · Server route (Next.js App Router)
// app/api/chat/route.ts
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.aix.theaimart.co/v1",
apiKey: process.env.AIX_KEY, // server-side only
});
export async function POST(req: Request) {
const { messages } = await req.json();
const stream = await client.chat.completions.create({
model: "deepseek-ai/DeepSeek-V3.2",
messages,
stream: true,
});
const encoder = new TextEncoder();
const body = new ReadableStream({
async start(controller) {
for await (const part of stream) {
const delta = part.choices[0]?.delta?.content ?? "";
if (delta) controller.enqueue(encoder.encode(delta));
}
controller.close();
},
});
return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
}
2 · Client component
"use client";
import { useState } from "react";
export default function Chat() {
const [out, setOut] = useState("");
async function send(prompt: string) {
setOut("");
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ messages: [{ role: "user", content: prompt }] }),
});
const reader = res.body!.getReader();
const dec = new TextDecoder();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
setOut((s) => s + dec.decode(value));
}
}
return (
<div>
<button onClick={() => send("Explain async Rust in 3 lines")}>Ask</button>
<pre>{out}</pre>
</div>
);
}
✦ Tip
This route flattens chunks to plain text for simplicity. To forward full OpenAI SSE
(so the client can use the OpenAI stream parser), pipe the upstream
text/event-stream through instead.
▲ Heads up
The AIx key lives only on the server (process.env.AIX_KEY). Don’t ship it to the browser.