HomePostsvercel puppeteer 2024

How to use Puppeteer with Serverless Vercel in 2024

Date
Last Updated
Profile Picture
Peter White@petewht
Contents

Puppeteer is a headless browser that allows you to automate tasks in a browser environment, such as testing, scraping, and generating screenshots, over the DevTools Protocol.

At Graphy, we use it to generate image exports of charts for customers. We made the choice to run it as a serverless function to avoid the need to maintain a Puppeeter instance.

The old way

Previously, running Chromium on Vercel was a bit of a pain because of function size limits on Vercel.

This required using a cut down Vercel executable like @sparticuz/chromium-min and loading the Chromium executable from your own URL.

You'll find lots of articles online explaining how to workaround this, but these are now thankfully outdated!

The new way

In 2024, Vercel has increased the size limit of functions to 250MB, which makes it possible to run Chromium on Vercel without major workarounds.

Install @sparticuz/chromium and Puppeeter, then use the code below, and call it from an API route:

import chromium from '@sparticuz/chromium';
 
// Keep in memory for possible re-use between innovations
let browser: puppeteer.Browser;
 
const chromeArgs = [
  // @sparticuz/chromium has default chromeArgs to improve serverless performance, but you can add others as you deem appropriate
  '--font-render-hinting=none', // Improves font-rendering quality and spacing
];
 
export const takeScreenshot = async ({
  width,
  height,
  url,
}: ScreenshotHandlerProps) => {
  const isLocal = false; // Set this variable as required - @sparticuz/chromium does not work on ARM, so we use a standard Chrome executable locally - see issue https://github.com/Sparticuz/chromium/issues/186
 
  if (!browser?.isConnected()) {
    // If you don't need webGL, this skips the extraction of the bin/swiftshader.tar.br file, improving performance
    chromium.setGraphicMode = false;
    browser = await puppeteer.launch({
      ...(isLocal
        ? { channel: 'chrome' }
        : {
            args: chromeArgs,
            executablePath: await chrome.executablePath(),
            ignoreHTTPSErrors: true,
          }),
    });
  }
 
  const page = await browser.newPage();
 
  await page.setViewport({
    width,
    height,
    deviceScaleFactor: 2,
  });
 
  const imagePage = await page.goto(url, { waitUntil: 'domcontentloaded' });
 
  if (!imagePage || !imagePage.ok()) {
    throw new Error('Failed to load image page');
  }
 
  const file = await page.screenshot({
    type: 'png',
    omitBackground: true,
  });
 
  const pages = await browser.pages();
 
  // Avoids orphaned pages by looping all open pages and closing them
  for (const openPage of pages) {
    await openPage.close();
  }
 
  // We don't want headless Chrome instances left running locally
  if (isLocal) {
    await browser.close();
  }
 
  return file;
};

Common issues

The AWS Lambda runtime that Vercel uses does not have any included fonts, though @sparticuz/chromium-min does helpfully add OpenSans for you!

If you see issues with character or emoji display:

  • Adding additional fonts to your application to cover the missing characters/emoji. For emoji, I recommend Noto Color Emoji
  • Load the fonts via the chromium.font method - at the time of publication, this is currently broken

If you see performance issues with running Chromium on serverless, consider:

If you see memory issues:


Thanks for Reading!

I always appreciate feedback or suggestions for future blog posts. You can find me on Twitter or if you want to improve the article to help future readers, please feel free to submit a PR.

🦉 2794 days

Duolingo Streak

Proudly created in beautiful Sætre in Norway 🇳🇴