Puppeteer生成PDF后如何通过API返回文件触发用户下载
Puppeteer生成PDF后触发前端自动下载的实现方法
核心调整点
- 原有代码调用
page.pdf()时传入了path参数,Puppeteer会直接将PDF写入服务器磁盘,不会返回文件二进制内容。如果不需要在服务器留存文件,直接去掉path参数即可拿到PDF的内存Buffer,省掉磁盘读写开销;如果需要留存服务器副本,保留path配置,生成后读取本地文件即可。 - 必须设置正确的HTTP响应头,告知浏览器当前返回的是可下载的PDF文件,不能再返回JSON格式的响应。
- 补充异常捕获和浏览器实例回收逻辑,避免出错时出现内存泄漏。
修改后完整代码
// 如果需要在服务器留存PDF文件,再引入fs模块,否则可以省略 const fs = require('fs/promises'); async function createPdf(req, res) { let browser; try { browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000', { timeout: 10000, waitUntil: ['load', 'domcontentloaded', 'networkidle0', 'networkidle2'], }); // ---------- 两种方案二选一即可 ---------- // 方案1:不在服务器存PDF,直接返回内存中的文件,性能更优 const pdfContent = await page.pdf({ landscape: false, format: 'A4', margin: { top: '0px', right: '0px', bottom: '0px', left: '0px', }, }); // 方案2:需要在服务器留存PDF时使用 // 注意不要单独用Math.random()生成文件名,重名概率高,推荐用时间戳/UUID组合 // const pdfSavePath = `./invoices/${Date.now()}_${Math.random().toString(36).slice(2)}.pdf`; // await page.pdf({ // path: pdfSavePath, // landscape: false, // format: 'A4', // margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }, // }); // const pdfContent = await fs.readFile(pdfSavePath); // -------------------------------- // 设置响应头,触发浏览器下载 res.setHeader('Content-Type', 'application/pdf'); // filename字段为用户下载时默认显示的文件名,可按需修改 res.setHeader('Content-Disposition', 'attachment; filename="invoice.pdf"'); res.status(200).send(pdfContent); } catch (error) { res.status(500).send({ success: false, message: 'PDF生成失败', errMsg: error.message }); } finally { // 无论生成成功还是失败,都关闭浏览器实例,避免内存泄漏 if (browser) await browser.close(); } }
补充说明
- 如果是通过
<a>标签直接跳转、window.open打开该接口地址,上述配置完成后即可直接触发浏览器下载,不需要额外写前端代码。 - 如果是通过fetch/axios等AJAX方式调用接口,前端需要将响应转换为Blob对象,生成临时URL后手动触发下载,否则不会弹出下载框。
- 采用本地存文件方案时,需要提前创建好
./invoices目录,否则Puppeteer写入文件时会抛出路径不存在的错误。
内容的提问来源于stack exchange,提问作者cutrimafyi
相关产品推荐
相关产品推荐

