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

Express/Puppeteer从EJS模板生成PDF异常:未渲染数据与格式

问题根源

你遇到的核心问题是:Puppeteer加载的是未经过Express渲染的原始EJS模板文件,EJS的变量标签(比如<%= name %>)只有通过Express的res.render或EJS引擎手动编译,才能转换成填充了用户数据的HTML。直接读取EJS文件内容,Puppeteer只会把它当成纯文本解析,自然显示原始模板代码。


两种解决方案

方案一:手动编译EJS模板,传递渲染后的HTML给Puppeteer

适合模板不依赖外部静态资源(如CSS、图片)的场景,直接把编译好的HTML字符串传给Puppeteer:

const express = require('express');
const router = express.Router();
const puppeteer = require('puppeteer');
const fs = require('fs');
const ejs = require('ejs');

router.get('/generate_invoice', async (req, res) => {
  // 示例用户数据
  const userData = {
    name: '张三',
    email: 'zhangsan@example.com',
    total: 300,
    items: [{desc: '基础服务', price: 200}, {desc: '增值服务', price: 100}]
  };

  try {
    // 读取EJS模板文件
    const templatePath = './views/invoice.ejs';
    const templateContent = fs.readFileSync(templatePath, 'utf8');
    
    // 用EJS引擎编译模板,注入用户数据得到完整HTML
    const renderedHtml = ejs.render(templateContent, userData);

    // 启动Puppeteer生成PDF
    const browser = await puppeteer.launch({ headless: 'new' });
    const page = await browser.newPage();
    // 加载编译后的HTML,等待页面完全就绪
    await page.setContent(renderedHtml, { waitUntil: 'networkidle0' });
    
    const pdfBuffer = await page.pdf({
      format: 'A4',
      printBackground: true, // 确保背景样式生效
      margin: { top: '20px', right: '20px', bottom: '20px', left: '20px' }
    });

    await browser.close();

    // 返回PDF给客户端
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="invoice.pdf"');
    res.send(pdfBuffer);
  } catch (err) {
    console.error(err);
    res.status(500).send('PDF生成失败');
  }
});

module.exports = router;

方案二:让Puppeteer访问已渲染的Express路由

如果你的EJS模板依赖外部静态资源(如自定义CSS、logo图片),直接传HTML字符串会丢失资源引用,这时候可以让Puppeteer访问同一个服务器上的渲染路由:

const express = require('express');
const router = express.Router();
const puppeteer = require('puppeteer');

// 新增专门渲染发票HTML的路由
router.get('/invoice_html', (req, res) => {
  const userData = {
    name: '张三',
    email: 'zhangsan@example.com',
    total: 300,
    items: [{desc: '基础服务', price: 200}, {desc: '增值服务', price: 100}]
  };
  res.render('invoice', userData);
});

// 生成PDF的路由
router.get('/generate_invoice', async (req, res) => {
  try {
    const browser = await puppeteer.launch({ headless: 'new' });
    const page = await browser.newPage();
    
    // 访问已渲染的HTML路由,替换成你的服务器实际地址和端口
    const invoiceUrl = `http://localhost:3000/invoice_html`;
    await page.goto(invoiceUrl, { waitUntil: 'networkidle0' });
    
    const pdfBuffer = await page.pdf({
      format: 'A4',
      printBackground: true,
      margin: { top: '20px', right: '20px', bottom: '20px', left: '20px' }
    });

    await browser.close();

    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="invoice.pdf"');
    res.send(pdfBuffer);
  } catch (err) {
    console.error(err);
    res.status(500).send('PDF生成失败');
  }
});

module.exports = router;

注意事项
  • 生产环境中,Puppeteer需要配置headless: 'new'(新版无界面模式),避免兼容性问题
  • 使用waitUntil: 'networkidle0'确保页面所有资源加载完成后再生成PDF,避免内容缺失
  • 模板中的静态资源请使用绝对路径(如/css/invoice.css),确保Puppeteer能正确加载

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:55:36