Demo, all content is generated
Question

OpenAI streaming works locally, on Vercel the whole answer appears at once after 15s

Open · 522 views · asked by tuan_ng · edited

Locally the chat response types out word by word. Deployed, the user stares at nothing for ~15 seconds and then the full answer pops in. Route handler (Claude Code wrote it):

export async function POST(req: Request) {
  const { messages } = await req.json();
  const stream = await openai.chat.completions.create({ model, messages, stream: true });
  let text = "";
  for await (const chunk of stream) text += chunk.choices[0]?.delta?.content ?? "";
  return new Response(text);
}
What I’ve tried

Claude said it's a Vercel limitation and suggested switching to the edge runtime. Same behavior on edge.

Comment
Are you using the Vercel AI SDK or the openai package directly? elif_y · edited
openai package directly tuan_ng · edited

3 answers

chidi_eze · edited

It's not Vercel, it's the loop. You read the entire stream into text on the server and only then return it. Locally it probably only looked like streaming because the client animates the text.

Pass the stream through instead:

export async function POST(req: Request) {
  const { messages } = await req.json();
  const stream = await openai.chat.completions.create({ model, messages, stream: true });
  const encoder = new TextEncoder();
  const body = new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        controller.enqueue(encoder.encode(chunk.choices[0]?.delta?.content ?? ""));
      }
      controller.close();
    },
  });
  return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
}

And on the client, read res.body.getReader() instead of await res.text(), otherwise the browser waits for the end as well.

Comment
Oh no, the client had the typewriter animation, you're right. Changed both sides, first words show after ~1s on production now. tuan_ng · edited
Nice. Switch back to the Node runtime too, no reason to be on edge for this. chidi_eze · edited
deploydan · edited

If it still buffers after that fix, look at what sits in front of the app. On Vercel it streams fine, but if you ever move to a VPS behind nginx, add the header X-Accel-Buffering: no or nginx collects the whole response first. Same idea with some Cloudflare settings.

Comment
Good to know, a client wants this self-hosted later. Saving this. tuan_ng · edited
lena_ops · edited

Also send Cache-Control: no-cache, no-transform on streamed responses. Some proxies and CDNs compress the response, and compression can make them buffer it first.

Comment