Skip to content

Instantly share code, notes, and snippets.

@kettanaito
Last active May 6, 2024 14:58
Show Gist options
  • Star 83 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save kettanaito/56861aff96e6debc575d522dd03e5725 to your computer and use it in GitHub Desktop.
Save kettanaito/56861aff96e6debc575d522dd03e5725 to your computer and use it in GitHub Desktop.
Chromium on Vercel (serveless)

Chromium on Vercel (serverless)

This is an up-to-date guide on running Chromium in Vercel serverless functions in 2022. What you will read below is the result of two days of research, debugging, 100+ failed deployments, and a little bit of stress.

Getting started

Step 1: Install dependencies

Use chrome-aws-lambda that comes with Chromium pre-configured to run in serverless, and puppeteer-core due to the smaller size of Chromium distributive.

Turns out, choosing the right versions of dependencies is crucial. Newer versions of puppeteer-core ship larger Chromium distributive, which will exceed the 50MB function size limit on Vercel.

{
  "chrome-aws-lambda": "10.1.0",
  // Install v10 to have a smaller Chromium distributive.
  "puppeteer-core": "10.1.0"
}

If you feel adventerous and wish to update dependencies, start from updating chrome-aws-lambda. Its peer dependency on puppeteer-core will tell you the maximum supported version to use.

Why not Playwright?

Playwright comes with a larger Chromimum instance that would exceed the maximum allowed serverless function size limit of 50MB on Vercel (transitively, AWS).

Step 2: Write a function

The way you write your function does not matter: it may be a Node.js function, a part of your Next.js /api routes, or a Remix application. What matters on this stage is to launch Puppeteer with correct options.

// api/run.js
import edgeChromium from 'chrome-aws-lambda'

// Importing Puppeteer core as default otherwise
// it won't function correctly with "launch()"
import puppeteer from 'puppeteer-core'

// You may want to change this if you're developing
// on a platform different from macOS.
// See https://github.com/vercel/og-image for a more resilient
// system-agnostic options for Puppeteeer.
const LOCAL_CHROME_EXECUTABLE = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'

export default async function (req, res) {
  // Edge executable will return an empty string locally.
  const executablePath = await edgeChromium.executablePath || LOCAL_CHROME_EXECUTABLE
  
  const browser = await puppeteer.launch({
    executablePath,
    args: edgeChromium.args,
    headless: false,
  })
  
  const page = await browser.newPage()
  await page.goto('https://github.com')
  
  res.send('hello')
}

Step 3: Configure Vercel deployment

Choose Node.js 14.x

puppeteer-core@10 doesn't work well with newer versions of Node.js. The pinned version should be Node.js 14.

  1. Go to your Vercel project.
  2. Go to "Settings", then "General".
  3. Locate the "Node.js version" section.
  4. Select "Node 14".

Screen Shot 2022-10-22 at 13 11 56

In addition to configuring this on Vercel, make sure that your project's package.json doesn't have the engines.node property that would contradict your choise:

{
  "engines": {
    // If you have a newer version of Node in your package.json
    // Vercel will respect that and disregard what you've set
    // in your project's settings.
    "node": "14.x"
  }
}

Basically, you should not see this warning in your deployment logs:

Warning: Detected "engines": { "node": ">=14" } in your `package.json` that will automatically upgrade when a new major Node.js Version is released. Learn More: http://vercel.link/node-version

If you do, this means your Node.js version is not configured correctly and you should repeat this section.


References


Troubleshooting

Serverless function exceeds the maximum size limit of 50mb

Error: The Serverless Function "XYZ" is XX.YYmb which exceeds the maximum size limit of 50mb. Learn More: https://vercel.link/serverless-function-size

This means you're deploying a Chromium executable that's too large. Solve this by Installing the right dependency versions.

libnss3.so

Error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory

This error means you're running an older/newer version of Node.js rather than Node.js 14. Solve this by Using the right Node.js version.

People on the web will suggest all sorts of things to tackle this, like installing libnss3.so manually, but fret not: it's solved by using the right version of Node.js.


If you found this helpful, consider Buying me a cup of coffee.

@jferrettiboke
Copy link

@exchai93 Are you using Next.js? If so, add this to your next.config.js, and restart your server.

/** @type {import('next').NextConfig} */
const config = {
  // ...
  
  experimental: {
    serverComponentsExternalPackages: [
      'puppeteer-core',
      '@sparticuz/chromium-min',
    ],
  },
};

export default config;

@gruckion
Copy link

gruckion commented Apr 3, 2024

Okay I've managed to get it to work. I am on an M1 Mac!

https://github.com/gruckion/puppeteer-running-in-vercel <- Repo

You will need Chromium on your machine.

brew install --cask chromium
If you run into issues use this guide here

TLDR?

  1. Bundle size needs to be 50mb or less so use puppeteer-core for production.
  2. Use @sparticuz/chromium for the chromium instance in Serverless environment
  3. If you need less than 50mb then use @sparticuz/chromium-min and host the tz yourself.
// src/app/api/route.ts

import { NextRequest, NextResponse } from "next/server";
import puppeteerCore from "puppeteer-core";
import puppeteer from "puppeteer";
import chromium from "@sparticuz/chromium";

export const dynamic = "force-dynamic";

async function getBrowser() {
  if (process.env.VERCEL_ENV === "production") {
    const executablePath = await chromium.executablePath();

    const browser = await puppeteerCore.launch({
      args: chromium.args,
      defaultViewport: chromium.defaultViewport,
      executablePath,
      headless: chromium.headless,
    });
    return browser;
  } else {
    const browser = await puppeteer.launch();
    return browser;
  }
}

export async function GET(request: NextRequest) {
  const browser = await getBrowser();

  const page = await browser.newPage();
  await page.goto("https://example.com");
  const pdf = await page.pdf();
  await browser.close();
  return new NextResponse(pdf, {
    headers: {
      "Content-Type": "application/pdf",
    },
  });
}
// next.config.mjs

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    serverComponentsExternalPackages: ['puppeteer-core', '@sparticuz/chromium'],
  }
};

export default nextConfig;
// package.json

{
  "name": "test-pdf-generate",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@sparticuz/chromium": "^123.0.0",
    "next": "14.1.4",
    "puppeteer": "^22.6.2",
    "puppeteer-core": "^22.6.2",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "autoprefixer": "^10.0.1",
    "eslint": "^8",
    "eslint-config-next": "14.1.4",
    "postcss": "^8",
    "tailwindcss": "^3.3.0",
    "typescript": "^5"
  }
}

Credits

Unexpected token in EmulationManager.js when used with Next.js

puppeteer/puppeteer#11052
Sparticuz/chromium#147 (comment)
https://nextjs.org/docs/app/api-reference/next-config-js/serverComponentsExternalPackages

Cannot spawn Chromium with custom executablePath on Apple M1

puppeteer/puppeteer#6634
Sparticuz/chromium#63 (comment)

@aymanechaaba1
Copy link

@gruckion Do you have to add VERCEL_ENV to your .env.local and your vercel environment variables settings?

@Christopher-Hayes
Copy link

Christopher-Hayes commented May 6, 2024

Anyone coming to this gist in 2024 - keep in mind this gist requires that you use Node v14 (2018) and Puppeteer v10 (2021). Right now, stable versions of those packages are Node v20 (2024), and Puppeteer v22 (2024). You probably do not want to use versions that old. Vercel doesn't even let you use Node v14 on functions anymore.

The comments do have good alternatives though. @sparticuz/chromium-min worked for me (I did the same thing as this code). Don't re-use the .tar link in that example, host it yourself. The supabase file download is a bit slow.

However, be ready for puppeteer to run slow on serverless functions though, longer function timeouts are probably needed. Vercel for example will be ~4x-8x slower than your dev machine. Unless you use more powerful Vercel function CPUs. For example, my script on the "Basic" CPU took ~50s, and the "Performance" CPU took ~10s.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment