Use this file to discover all available pages before exploring further.
Next.js is a popular way to write frontend UIs backed by serverless functions. Streamstraight integrates easily, allowing you to stream LLM responses from your server to your client, all within Next.js.
Modify your server route to kick off your LLM generation and pipe its stream into Streamstraight. This should be whichever route receives and handles the user’s prompt.
app/api/chat/route.ts
import { streamstraightServer } from "@streamstraight/server";export async function POST(request: Request) { const bodyJson = (await request.json()) as { messageId: string; userInput: string }; const { messageId, userInput } = bodyJson; const llmResponseStream = await openai.responses.stream({ model: "gpt-4.1-mini", input: [{ role: "user", content: [{ type: "text", text: userInput }] }], }); const ssServer = await streamstraightServer( { apiKey: process.env.STREAMSTRAIGHT_API_KEY }, { streamId: messageId }, ); // Run this in the background so you don't block your network request. void ssServer.stream(llmResponseStream); return Response.json({ messageId });}
In your chat UI, you’ll need to ensure a few things happen:
Your client fetches a Streamstraight client JWT
Your client has a unique streamId or messageId that is the same as what’s on the server
Your client connects to the Streamstraight and handles the resulting chunks
You can do these however you want; below is an example:
app/(chat)/page.tsx
"use client";import { createId } from "@paralleldrive/cuid2";import { connectStreamstraightClient } from "@streamstraight/client";import type OpenAI from "openai";import { useCallback, useState } from "react";async function fetchStreamstraightToken(): Promise<string> { const response = await fetch("/api/streamstraight-token", { method: "POST" }); if (!response.ok) { throw new Error("Failed to fetch Streamstraight token"); } const { jwtToken } = (await response.json()) as { jwtToken: string }; return jwtToken;}export default function AiChatPage() { const [textInput, setTextInput] = useState<string>(""); const [assistantResponse, setAssistantResponse] = useState<string>(""); function handleSendMessage({ userInput }: { userInput: string }) { const messageId = createId(); // Assign a unique ID for this message stream // You can start listening for the stream in the background before // making the network request to your backend. void handleMessageStream({ messageId }); // Make the network request to your backend to start LLM generation. fetch("/api/chat", { method: "POST", body: JSON.stringify({ messageId, userInput }), }); } async function handleMessageStream({ messageId }: { messageId: string }) { const ssClient = // Type each chunk however you want; you're not tied to the OpenAI example here // Just make sure the type matches whats on your server await connectStreamstraightClient<OpenAI.Responses.ResponseStreamEvent>( { fetchToken: fetchStreamstraightToken }, { streamId: messageId }, ); for await (const chunk of ssClient.toAsyncIterable()) { // Handle each chunk as it's streamed. The example code below // demonstrates handling the OpenAI response stream. if (chunk.type !== "response.output_text.delta") continue; setAssistantResponse((prev) => prev + chunk.delta); } } return ( <div> {/* Your chat UI */} <input value={textInput} onChange={(e) => setTextInput(e.target.value)} /> <Button onClick={() => handleSendMessage({ userInput: textInput })}>Send</Button> </div> );}