PejmanNik/puppeteer-report

Running in a Netlify function (lambda)

Closed this issue · 3 comments

Hello again,

After solving this issue #22 I've been continuing to use this library with great success.

I've got a website, with a bunch of pages on there, and I need a PDF version of each of them.

I'm building the pages with Eleventy (static site generator) and then basing the PDFs off a set of specific pages on the site. The site is built/hosted with Netlify.
For a while I had a simple script which would loop over each page and run puppeteer report for each one, generating me a PDF for each. This was working fine until my list of pages exceeded a certain number (maybe like 50?) and then the Netlify build script would tap out. Locally it would run around 100 pages, but couldn't exceed that (and it made the fans go a bit crazy!).

So not really a sustainable solution, I've now decided to look into using Netlify functions to try and build the PDFs one at a time when the user requests them instead.

I've got a basic function running, which seems to be working correctly, but if I try to create a PDF with puppeteer-report it doesn't work properly. However, if I just use puppeteer's built in pdf method, it's fine.

This is my Netlify function:

const chromium = require("chrome-aws-lambda");
const report = require("puppeteer-report");

exports.handler = async (event, context) => {
  const pageUrlToPdf = JSON.parse(event.body).pageUrlToPdf;

  if (!pageUrlToPdf)
    return {
      statusCode: 400,
      body: JSON.stringify({ message: "Page URL not defined" }),
    };

  const browser = await chromium.puppeteer.launch();

  const page = await browser.newPage();

  await page.goto(pageUrlToPdf, { waitUntil: "networkidle2" });

  const pdf = await report.pdfPage(page, {
    printBackground: true,
    format: "A4",
  });

  await browser.close();

  const response = {
    headers: {
      "Content-type": "application/pdf",
      "content-disposition": "attachment; filename=test.pdf",
    },
    statusCode: 200,
    body: pdf.toString("base64"),
    isBase64Encoded: true,
  };

  context.succeed(response);
};

And this is my client-side JS fetch call and response handling

    fetch("/.netlify/functions/take-screenshot", options)
      .then((res) => res.blob())
      .then((res) => {
        const link = document.createElement("a");
        const url = window.URL.createObjectURL(res, {
          type: "application/pdf",
        });

        link.setAttribute("href", url);
        //   link.setAttribute("download", "download");
        link.innerHTML = "Download";
        document.getElementById("result").innerHTML = link.outerHTML;
      })
      .catch((err) => {
        console.log(err);
        document.getElementById(
          "result"
        ).textContent = `Error: ${err.toString()}`;
      });

If I change the Netlify function to just the puppeteer pdf method it works fine:

const chromium = require("chrome-aws-lambda");

exports.handler = async (event, context) => {
  const pageUrlToPdf = JSON.parse(event.body).pageUrlToPdf;

  if (!pageUrlToPdf)
    return {
      statusCode: 400,
      body: JSON.stringify({ message: "Page URL not defined" }),
    };

  const browser = await chromium.puppeteer.launch();

  const page = await browser.newPage();

  await page.goto(pageUrlToPdf, { waitUntil: "networkidle2" });

  const pdf = await page.pdf(page, { // changed this!
    printBackground: true,
    format: "A4",
  });

  await browser.close();

  const response = {
    headers: {
      "Content-type": "application/pdf",
      "content-disposition": "attachment; filename=test.pdf",
    },
    statusCode: 200,
    body: pdf.toString("base64"),
    isBase64Encoded: true,
  };

  context.succeed(response);
};

I'm assuming it's something to do with what report.pdfPage returns vs what page.pdf returns? I know nothing at all about byte arrays/buffers, but it seems page.pdf returns a buffer and that can be easily base64 encoded and sent in the response, but report.pagePdf returns a byte array and I don't know what I need to do with that to make it into something I can base64 encode?

Help would be appreciated!

Hi, good to hear that.

The output is Uint8Array you can simply convert it to base64. for instance:

const result = await report.pdf(....
const buffer = Buffer.from(result);
const base64 =  buffer.toString("base64");

Oh nice! Thanks so much. Very simple indeed!

You're welcome @martynhoyer 😁👍