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

html-pdf服务端生成PDF正常 客户端下载后为空白页问题

问题根因

你遇到的PDF空白、文件损坏问题全部来自代码实现错误,和html-pdf库本身无关,核心错误点如下:

  • response.sendFile() 用法错误:该方法第一个参数必须是文件系统中真实存在的文件绝对路径,你将路径字符串转成base64后传入,属于无效参数,根本没有正确读取到生成的PDF文件。
  • 异步逻辑错误:pdf.create().toFile() 是回调风格API,本身不返回Promise,你用Promise.all包裹后外层await根本无法等待PDF生成完成,存在响应发送时机不对、内容传输不完整的问题。
  • 前端请求配置缺失:axios默认会将二进制响应当成UTF-8文本解析,会直接破坏PDF的二进制结构,这就是典型的字节截断问题,也是页数正确但全空白的核心诱因。
  • 代码存在冗余和变量冲突:你读取文件转buffer的逻辑写完后完全没有使用,同时回调内参数命名和外层响应对象重名,很容易触发隐式的变量引用错误。
  • 路径拼接不规范:手动拼接路径没有用path.join处理跨平台分隔符,很容易出现路径找不到文件的问题。
修复实现

服务端修正

不需要转base64,直接以二进制流返回文件即可,传输效率比base64高30%以上,修正后的代码:

const pdf = require("html-pdf");
const asyncHandler = require("../middleware/async");
const fs = require("fs");
const path = require("path");

const Deck = require("../models/deckModel");
const DeckListTemp = require("../templates/DeckListTemp");
const Dynamic = require("../models/dynamicModel");

/**
 * @description Generate PDF document of deck list
 * @param       {Number} deckId - Deck ID
 * @returns     {String} - PDF document
 * @author      Austin Howard
 * @version     1.0.0
 * @since       1.0.0
 * @date        2022-5-30
 *
 */
module.exports = asyncHandler(async (req, res, next) => {
  try {
    // find the deck
    const deck = await Deck.findById(req.params.deckId);
    // get the logo of the application
    const logo = await Dynamic.findOne({ type: "Logo" });
    // check to see if the deck and logo exist
    if (!deck || !logo) {
      return res.status(404).json({
        message: "Cannot find deck or logo on Server",
      });
    }

    // sort cards by name
    deck.cards.sort((a, b) => {
      if (a.name < b.name) return -1;
      if (a.name > b.name) return 1;
      return 0;
    });

    // pass the deck to the template
    const html = DeckListTemp(deck, logo.value);

    // sanitize deck name for file path
    const safeDeckName = deck.deck_name.replace(/\//g, " ").substring(0, 50);
    const pdfPath = path.join(__dirname, "../../../public/pdf", `${safeDeckName}.pdf`);

    // wrap pdf generation in promise to properly await
    await new Promise((resolve, reject) => {
      pdf
        .create(html, {
          encoding: "UTF-8",
          format: "A3",
          border: {
            top: "1in",
            right: "1in",
            bottom: "1in",
            left: "1in",
          },
        })
        .toFile(pdfPath, (err, result) => {
          if (err) reject(err);
          resolve(result);
        });
    });

    // set response headers and send file
    res.setHeader("Content-Type", "application/pdf");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="${encodeURIComponent(deck.deck_name)}.pdf"`
    );
    
    res.sendFile(pdfPath, (err) => {
      if (err) {
        console.error("PDF send error:", err);
        return res.status(500).json({ success: false, message: "Failed to send PDF" });
      }
      // optional: delete temp file after send completes
      // fs.unlinkSync(pdfPath);
    });

  } catch (error) {
    console.error(error);
    res.status(500).json({
      success: false,
      message: `Server Error - ${error.message}`,
    });
  }
});

注意:文件名在Content-Disposition中需要用encodeURIComponent处理,否则包含特殊字符、中文时会出现文件名乱码问题。

前端修正

axios必须指定响应类型为blob,避免二进制数据被文本解析破坏,修正后的代码:

import axios from "axios";
import {
  DECK_PREVIEW_REQUEST,
  DECK_PREVIEW_SUCCESS,
} from "../../constants/deckConstants";
import { setAlert } from "../../utils/alert";

export const generatePdf = (deck) => async (dispatch) => {
  try {
    dispatch({ type: DECK_PREVIEW_REQUEST });
    const { data } = await axios.post(
      `/api/deck/${deck._id}/pdf`,
      {},
      {
        responseType: "blob", // 核心配置:指定响应为二进制blob类型
        headers: {
          "Content-Type": "application/json",
        },
      }
    );

    // create blob link to download
    const url = window.URL.createObjectURL(
      new Blob([data], { type: "application/pdf" })
    );
    const link = document.createElement("a");
    link.href = url;
    link.setAttribute("download", `${deck.deck_name.replace(/\//g, " ")}.pdf`);

    document.body.appendChild(link);
    link.click();

    // clean up
    window.URL.revokeObjectURL(url);
    link.parentNode.removeChild(link);
    
    dispatch({ type: DECK_PREVIEW_SUCCESS });
  } catch (error) {
    console.error(error);
    dispatch(setAlert(`problem generating pdf ${error}`, "danger"));
  }
};

注意:下载完成后需要调用window.URL.revokeObjectURL释放内存,避免长时间使用出现内存泄漏。

验证流程
  • 修复后先直接在浏览器访问PDF生成接口,确认直接下载的文件可以正常打开,排除服务端问题。
  • 对比前端下载的文件大小和服务端本地生成的PDF文件大小,大小完全一致说明传输过程没有字节丢失。
  • 如果仍有异常,先注释掉临时文件删除逻辑,确认服务端存储的本地PDF文件本身内容正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 10:30:52