node-html-pdf: Error deploying to Vercel / AWS Lambda

In my PC (Kubuntu 20.04) it works perfectly. The problem appears when I deploy to Vercel. To begin with, I need to set phantomPath, otherwise I get a “write EPIPE” error:

pdf.create(html, {
  phantomPath: path.resolve(
    process.cwd(),
    "node_modules/phantomjs-prebuilt/bin/phantomjs"
  ),
})

Now, if I run it, it throws:

html-pdf: Received the exit code '1'
Error executing phantom at /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
Error: spawn /var/task/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs ENOENT
    at Process.ChildProcess._handle.onexit (internal/child_process.js:267:19)
    at onErrorNT (internal/child_process.js:469:16)
    at processTicksAndRejections (internal/process/task_queues.js:84:21)
events.js:287
      throw er; // Unhandled 'error' event
      ^

Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyed
    at doWrite (_stream_writable.js:399:19)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at Socket.ondata (_stream_readable.js:695:22)
    at Socket.emit (events.js:310:20)
    at addChunk (_stream_readable.js:286:12)
    at readableAddChunk (_stream_readable.js:268:9)
    at Socket.Readable.push (_stream_readable.js:209:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:186:23)
Emitted 'error' event on Socket instance at:
    at errorOrDestroy (internal/streams/destroy.js:108:12)
    at Socket.onerror (_stream_readable.js:729:7)
    at Socket.emit (events.js:310:20)
    at errorOrDestroy (internal/streams/destroy.js:108:12)
    at onwriteError (_stream_writable.js:418:5)
    at onwrite (_stream_writable.js:445:5)
    at doWrite (_stream_writable.js:399:11)
    at writeOrBuffer (_stream_writable.js:387:5)
    at Socket.Writable.write (_stream_writable.js:318:11)
    at Socket.ondata (_stream_readable.js:695:22) {
  code: 'ERR_STREAM_DESTROYED'
}

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 5
  • Comments: 27

Most upvoted comments

I forgot to mention it, but I found a (hacky) solution:

First, you add a fonts.conf wherever you have your fonts (remember to change the dir):

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <dir>/var/task/my-font-directory/</dir>
  <cachedir>/tmp/fonts-cache/</cachedir>
  <config></config>
</fontconfig>

Then, you download these libs (the same from here, but in a ZIP) and put them in a folder. I put them in /bins.

Finally, before you run pdf.create(), set these env vars:

process.env.FONTCONFIG_PATH = path.join(process.cwd(), "my-font-directory");
process.env.LD_LIBRARY_PATH = path.join(process.cwd(), "bins");
Dev Mode

If your PDF throws some error in Dev mode, you can set the env vars only in production mode. Like this:

if (process.env.NODE_ENV === "production") {
  process.env.FONTCONFIG_PATH = path.join(process.cwd(), "my-font-directory");
  process.env.LD_LIBRARY_PATH = path.join(process.cwd(), "bins");
}

Also, remember to change the phantomPath!!!

pdf.create(html, {
  phantomPath: path.resolve(
    process.cwd(),
    "node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs"
  ),
})

I ended up using puppeteer-- was really straight forward.

let puppeteer;

if (process.env.AWS_LAMBDA_FUNCTION_VERSION) {
  // running on the Vercel platform.
  chrome = require('chrome-aws-lambda');
  puppeteer = require('puppeteer-core');
} else {
  // running locally.
  puppeteer = require('puppeteer');
}

const handler = async (req, res) => {

    // https://github.com/vercel/vercel/discussions/4903#discussioncomment-234166
    const browser = await puppeteer.launch({
        args: [...chrome.args, '--hide-scrollbars', '--disable-web-security'],
        defaultViewport: chrome.defaultViewport,
        executablePath: await chrome.executablePath,
        headless: true,
        ignoreHTTPSErrors: true,
      });
    const page = await browser.newPage();
    await page.goto('https://www.example.com');
    const pdf = await page.pdf({
        format: 'A4',
        orientation: 'portrait',
        margin: {
            top: "10mm",
            right: "10mm",
            bottom: "10mm",
            left: "10mm",
        },
        timeout: 30000,
    })
    await browser.close();
    res.setHeader('Content-disposition', `attachment; filename="test.pdf`)
    res.setHeader('Content-Type', 'application/pdf')
    res.end(pdf)
}

export default handler

Borrowed a little from here https://github.com/vercel/vercel/discussions/4903#discussioncomment-234166

Perfect! I edited the comment for new people. Now it should be right

Oh, wait. I think I was wrong about the phantomPath. Try this: node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs

(sorry for no answering sooner, my PC crashed)