You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node 17及以下版本的Next.js中实现OpenAI流式响应传输

问题解决:Next.js API路由实现OpenAI流式响应(Node 17及以下版本)

问题背景

由于DigitalOcean App Platform暂不支持Node 18,只能使用Node 17及以下版本。尝试过openai-streams+nextjs-openai方案,但该方案仅兼容Node 18+,低版本无法运行。客户端直接调用OpenAI API虽能正常工作,但会暴露API密钥,因此需要在Next.js API路由中实现流式响应传输,但当前代码仅能获取第一块响应,无法实现ChatGPT式的流式输出。

问题分析与修复

1. API路由代码修复(/api/prompt.js)

原代码存在两个核心问题:

  • 错误使用Readable.from(string).pipe(res),无法正确逐块输出流式内容
  • 未设置流式响应所需的HTTP头,且API密钥使用了NEXT_PUBLIC_前缀(会暴露给客户端,存在安全风险)

修复后的代码:

import { Configuration, OpenAIApi } from "openai";

// 注意:不要用NEXT_PUBLIC_前缀,避免密钥暴露
const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

export default async function handler(req, res) {
  // 设置流式响应的HTTP头
  res.writeHead(200, {
    "Content-Type": "text/plain; charset=utf-8",
    "Transfer-Encoding": "chunked",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });

  try {
    const completion = await openai.createCompletion(
      {
        model: "text-davinci-003",
        prompt: "tell me a story",
        max_tokens: 500,
        stream: true,
      },
      { responseType: "stream" }
    );

    completion.data.on("data", (data) => {
      const lines = data.toString().split("\n").filter(line => line.trim() !== "");
      
      for (const line of lines) {
        const message = line.replace(/^data: /, "");
        if (message === "[DONE]") {
          res.end(); // 结束响应
          return;
        }
        try {
          const parsed = JSON.parse(message);
          const string = parsed.choices[0].text;
          res.write(string); // 直接将文本块写入响应流
        } catch (error) {
          console.error("解析流消息失败", message, error);
        }
      }
    });

    // 处理流错误
    completion.data.on("error", (err) => {
      console.error("流式请求错误", err);
      res.end(`Error: ${err.message}`);
    });

    // 处理连接关闭
    req.on("close", () => {
      completion.data.destroy();
      res.end();
    });
  } catch (err) {
    console.error("API请求错误", err);
    res.status(500).end(`Server Error: ${err.message}`);
  }
}

2. 前端组件代码修复(/components/Completion.js)

原代码使用response.text()会等待整个响应完成后才获取数据,无法实现流式更新。需要使用ReadableStream逐块读取响应内容:

import { useState } from "react";

export default function Completion() {
  const [text, setText] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const generate = async () => {
    setIsLoading(true);
    setText(""); // 清空之前的内容

    try {
      const response = await fetch("/api/prompt");
      if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);

      const reader = response.body.getReader();
      const decoder = new TextDecoder("utf-8");

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        
        const chunk = decoder.decode(value, { stream: true });
        setText(prev => prev + chunk); // 逐块更新文本
      }
    } catch (err) {
      setText(`生成失败:${err.message}`);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div>
      <button onClick={generate} disabled={isLoading}>
        {isLoading ? "生成中..." : "生成故事"}
      </button>
      <div style={{ marginTop: "1rem", whiteSpace: "pre-wrap" }}>{text}</div>
    </div>
  );
}

关键说明

  • API路由中必须设置Transfer-Encoding: chunked和Cache-Control: no-cache,确保浏览器能正确处理流式响应
  • 前端使用getReader()和TextDecoder逐块读取响应流,实现实时更新文本内容
  • 务必避免将OpenAI密钥暴露给客户端,不要使用NEXT_PUBLIC_前缀命名密钥环境变量

内容的提问来源于stack exchange,提问作者samchuang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:40:27