Next.js API路由返回CSV仅含表头的问题排查求助
问题描述
我理解的文件下载流程是前端调用API路由,其余操作在服务器端完成(若有误请指正)。我的任务是从Firestore读取数据并写入CSV文件,本地生成的文件数据完整,但前端下载后仅包含name和email表头。以下是我的API路由代码:
import { NextApiHandler } from "next"; import fs from "fs"; import { stringify } from "csv-stringify"; import { firestore } from "../../firestore"; import { unstable_getServerSession } from "next-auth/next"; import { authOptions } from "./auth/[...nextauth]"; const exportFromFirestoreHandler: NextApiHandler = async (req, res) => { const session = await unstable_getServerSession(req, res, authOptions); if (!session) { return res.status(401).json({ message: "You must be authorized here" }); } const filename = "guestlist.csv"; const writableStream = fs.createWriteStream(filename); const columns = ["name", "email"]; const stringifier = stringify({ header: true, columns }); const querySnapshot = await firestore.collection("paprockibrzozowski").get(); await querySnapshot.docs.forEach((entry) => { stringifier.write([entry.data().name, entry.data().email], "utf-8"); }); stringifier.pipe(writableStream); const csvFile = await fs.promises.readFile( `${process.cwd()}/${filename}`, "utf-8" ); res.status(200).setHeader("Content-Type", "text/csv").send(csvFile); }; export default exportFromFirestoreHandler;
问题原因与解决方案
核心问题是流操作的异步未等待完成:你在触发CSV写入流后立刻读取文件,但此时数据还没完全写入磁盘,所以前端只能拿到提前生成的表头。
错误点拆解
querySnapshot.docs.forEach是同步遍历,虽然querySnapshot已await,但stringifier.write是异步流操作,forEach不会等待所有数据写入完成。stringifier.pipe(writableStream)后直接执行readFile,此时文件写入还未结束,读到的内容不完整。
最优修正方案(直接流式返回,无需本地文件)
不需要生成本地文件,直接将CSV流通过响应对象返回给前端,既节省磁盘IO,又能确保数据完整:
import { NextApiHandler } from "next"; import { stringify } from "csv-stringify"; import { firestore } from "../../firestore"; import { unstable_getServerSession } from "next-auth/next"; import { authOptions } from "./auth/[...nextauth]"; const exportFromFirestoreHandler: NextApiHandler = async (req, res) => { const session = await unstable_getServerSession(req, res, authOptions); if (!session) { return res.status(401).json({ message: "You must be authorized here" }); } const columns = ["name", "email"]; const stringifier = stringify({ header: true, columns }); // 设置响应头,告诉前端这是CSV文件 res.setHeader("Content-Type", "text/csv"); res.setHeader("Content-Disposition", 'attachment; filename="guestlist.csv"'); // 将stringifier流直接管道到响应对象 stringifier.pipe(res); const querySnapshot = await firestore.collection("paprockibrzozowski").get(); querySnapshot.docs.forEach((entry) => { stringifier.write([entry.data().name, entry.data().email], "utf-8"); }); // 结束流,确保所有数据发送完成 stringifier.end(); }; export default exportFromFirestoreHandler;
若需保留本地文件的替代方案
如果必须生成本地文件后再返回,需要用Promise包裹流的finish事件,等待写入完成后再读取:
// 替换原代码中pipe和readFile的部分 await new Promise((resolve) => { stringifier.pipe(writableStream).on("finish", resolve); // 结束stringifier,触发流的finish事件 stringifier.end(); }); const csvFile = await fs.promises.readFile(`${process.cwd()}/${filename}`, "utf-8"); res.status(200).setHeader("Content-Type", "text/csv").send(csvFile);
内容的提问来源于stack exchange,提问作者seven
相关产品推荐
相关产品推荐

