Skip to main content
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.

1. Install Streamstraight packages

Add Streamstraight’s server SDK to the workspace that hosts your Next.js route handlers.
npm install --save @streamstraight/server @streamstraight/client
Create a .env entry for the API key you generate in the Streamstraight dashboard. In production, make sure to add this API key to your server secrets.
STREAMSTRAIGHT_API_KEY=your-api-key

2. Modify your Next.js server route handler

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.
https://nextjs.org/favicon.icoapp/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 });
}

3. Expose a token-minting route

Clients connect to Streamstraight with a short-lived JWT. Use your API key to mint those tokens in a protected route handler.
https://nextjs.org/favicon.icoapp/api/streamstraight-token/route.ts
import { fetchClientToken } from "@streamstraight/server";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  // Ensure your user is authenticated before minting tokens
  const token = await fetchClientToken({
    apiKey: process.env.STREAMSTRAIGHT_API_KEY,
  });

  return NextResponse.json({ token });
}

4. Connect to the stream from your client

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:
https://nextjs.org/favicon.icoapp/(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";

export default function AiChatPage() {
  const [textInput, setTextInput] = useState<string>("");
  const [assistantResponse, setAssistantResponse] = useState<string>("");

  const fetchStreamstraightToken = useCallback(async () => {
    const response = await fetch("/api/streamstraight-token", { method: "POST" });
    if (!response.ok) {
      throw new Error("Failed to fetch Streamstraight token");
    }
    const data = (await response.json()) as { token: string };
    return data.token;
  }, []);

  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>
  );
}