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
相关产品推荐
相关产品推荐

