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

Next.js 13 API路由如何正确返回Blob/File作为响应

解决Next.js 13 API路由返回Blob文件异常问题

问题现象

在Next.js 13的API路由中,尝试转发来自第三方API的Blob文件时,前端收到的Blob大小仅为2(内容为{}),但后端日志显示上游返回的Blob大小正常。

后端API路由代码:

export default async function handler(req, res) {
  const endpoint = `${process.env.UI_ENGINE}/docs/${req.query.id}/download-doc`;

  const response = await fetch(endpoint, {
    headers: { Authorization: `${req.headers.authorization}` },
  });

  const blob = await response.blob();

  console.log("»»»»»» [DEBUG : blob ] -- :", blob); // Blob { size: 1545, type: 'application/json' }

  res.status(response.status).send(blob);
}

前端调用代码:

const fetchWithToken = async (service, token) => {
  // 前端的数据获取函数
  const response = await fetch(`${process.env.basePath}/${service}`, {
    headers: { Authorization: `Bearer ${token}` },
  });

  const blob = await response.blob();

  console.log("»»»»»» [DEBUG : response ] --  :", blob); // Blob {size: 2, type: 'application/json'}

  // 文件下载逻辑 ....
}

问题原因

Next.js的res.send()方法在处理Blob对象时,会将其序列化为JSON格式。由于Blob对象本身不具备可序列化的属性,最终会被转化为{},前端收到的就是这个空对象的JSON字符串(大小为2),导致文件内容丢失。

最优解决方案:直接转发响应流

不需要将上游响应转为Blob,直接转发响应流是效率最高的方式,同时避免序列化问题。修改API路由代码如下:

export default async function handler(req, res) {
  const endpoint = `${process.env.UI_ENGINE}/docs/${req.query.id}/download-doc`;

  const upstreamResponse = await fetch(endpoint, {
    headers: { Authorization: `${req.headers.authorization}` },
  });

  // 复制上游响应的状态码和响应头
  res.status(upstreamResponse.status);
  // 转发Content-Type、Content-Length等必要头信息
  for (const [key, value] of upstreamResponse.headers) {
    res.setHeader(key, value);
  }

  // 获取上游响应的可读流并直接转发给前端
  const stream = upstreamResponse.body;
  await stream.pipeTo(new WritableStream({
    write(chunk) {
      res.write(chunk);
    },
    close() {
      res.end();
    }
  }));
}

方案优势

  • 内存高效:不需要将整个文件加载到内存(Blob会占用内存),流式传输适合大文件
  • 避免序列化问题:直接传递原始二进制数据,不会出现内容丢失
  • 保留完整响应信息:原样转发上游的状态码和响应头,确保前端能正确识别文件类型

备选方案:将Blob转为Buffer返回

如果必须先处理Blob(比如需要修改内容),可以将Blob转为Buffer后再发送:

export default async function handler(req, res) {
  const endpoint = `${process.env.UI_ENGINE}/docs/${req.query.id}/download-doc`;

  const upstreamResponse = await fetch(endpoint, {
    headers: { Authorization: `${req.headers.authorization}` },
  });

  const blob = await upstreamResponse.blob();
  // 将Blob转为ArrayBuffer,再转为Buffer
  const arrayBuffer = await blob.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  // 设置响应头
  res.status(upstreamResponse.status);
  res.setHeader('Content-Type', blob.type);
  res.setHeader('Content-Length', blob.size);

  // 发送Buffer
  res.send(buffer);
}

这个方案适合需要对Blob内容进行修改的场景,但相比流式转发,会占用更多内存,不适合大文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 11:10:19