Skip to content

Instantly share code, notes, and snippets.

@kettanaito
Last active April 26, 2024 08:01
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.

@bartosjiri
Copy link

@bartosjiri @sparticuz/chromium pushes the limit beyond 50mb when deployed. Can you share your package.json? maybe you're using specific versions of puppeteer/chromium

pnpm-lock.yaml:

# ...

dependencies:
  '@sparticuz/chromium':
    specifier: ^92.0.0
    version: 92.0.0
  dotenv:
    specifier: ^16.0.3
    version: 16.3.1
  puppeteer-core:
    specifier: 10.0.0
    version: 10.0.0

devDependencies:
  vercel:
    specifier: ^30.2.3
    version: 30.2.3(@types/node@14.18.33)
        
# ...

@ryan-nauman
Copy link

use the -min version specifically meant for 50mb limits? https://www.npmjs.com/package/@sparticuz/chromium-min

@martin-dimi
Copy link

@ryan-nauman it is, but hosting chromium on S3 and having the serverless function download at runtime considerably slows down it's performance on cold boots (by 2-5seconds)

@martin-dimi
Copy link

martin-dimi commented Jul 26, 2023

Okay, I can confirm that replacing chrome-aws-lambda": "10.1.0 with "@sparticuz/chromium": "92.0.0" works.
It keeps the size under 50MB (barely) and allows for node 18. Seems to work with playwright too "playwright-core": "1.31.1".

Will definitely need a proper long-term solution tho. With this, there's not much space for other dependencies...

@ndroo
Copy link

ndroo commented Aug 3, 2023

when i switch to sparticuz/chromium i get this any time i try navigate to a page and then capture a screenshot

locally mine works great, but on production with vercel no good.

    at /Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/puppeteer/common/LifecycleWatcher.js:51:147
    at /Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/vendor/mitt/src/index.js:51:62
    at Array.map (<anonymous>)
    at Object.emit (/Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/vendor/mitt/src/index.js:51:43)
    at CDPSession.emit (/Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/puppeteer/common/EventEmitter.js:72:22)
    at CDPSession._onClosed (/Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:256:14)
    at Connection._onClose (/Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/puppeteer/common/Connection.js:138:21)
    at WebSocket.<anonymous> (/Users/andrewmcgrath/mave/screencapture/node_modules/puppeteer-core/lib/cjs/puppeteer/node/NodeWebSocketTransport.js:17:30)
    at WebSocket.onClose (/Users/andrewmcgrath/mave/screencapture/node_modules/ws/lib/event-target.js:136:16)
    at WebSocket.emit (node:events:526:28)

@baretata
Copy link

baretata commented Aug 9, 2023

Okay, I can confirm that replacing chrome-aws-lambda": "10.1.0 with "@sparticuz/chromium": "92.0.0" works. It keeps the size under 50MB (barely) and allows for node 18. Seems to work with playwright too "playwright-core": "1.31.1".

Will definitely need a proper long-term solution tho. With this, there's not much space for other dependencies...

How did you manage to keep under 50mb when chromium + nextjs 13 is already above 50?

Anyone managed to run @sparticuz/chromium-min? It requires a chromium download and if the executablePath has a url to download, it exceeds the execution limit on vercel.

@teddis
Copy link

teddis commented Aug 18, 2023

@kettanaito, good stuff! But as of today, I still can't package this up < 50mb, even when backtracking with chrome-aws-lambda 2.1.1 at 41.23mb. Next dist is over 4mb and total is Error: The Serverless Function "api/inngest/func_li_master" is 51.44mb. Argh. This is my Vercel build log. Is there any way to remove possible excess dependencies? E.g. do we need @mui (?) I guess these are indirect deps we can't control in serverless?

Serverless Function's page: api/inngest.js
--
15:29:23.993 | Large Dependencies                            Uncompressed size  Compressed size
15:29:23.993 | node_modules/chrome-aws-lambda/bin                     41.27 MB         41.23 MB
15:29:23.993 | node_modules/next/dist                                 16.88 MB          4.19 MB
15:29:23.993 | node_modules/puppeteer-core/lib                         1.57 MB        439.51 KB
15:29:23.993 | node_modules/react-dom/cjs                              1.64 MB        404.02 KB
15:29:23.993 | node_modules/@mui/material                              1.21 MB        376.59 KB
15:29:23.993 | node_modules/lambdafs/build                           818.19 KB        361.82 KB
15:29:23.993 | node_modules/caniuse-lite/data                        913.78 KB        327.31 KB
15:29:23.993 | node_modules/@tootallnate/quickjs-emscripten          722.71 KB        300.82 KB
15:29:23.993 | node_modules/@mui/base                                610.62 KB        180.35 KB
15:29:23.993 |  
15:29:23.993 | All dependencies                                       76.62 MB         50.55 MB
15:29:23.994 | Serverless Function's page: api/inngest/func_li_master.js
15:29:24.005 | Large Dependencies                            Uncompressed size  Compressed size
15:29:24.005 | node_modules/chrome-aws-lambda/bin                     41.27 MB         41.23 MB
15:29:24.005 | node_modules/next/dist                                 16.88 MB          4.19 MB
15:29:24.005 | node_modules/puppeteer-core/lib                         1.57 MB        439.51 KB
15:29:24.005 | node_modules/react-dom/cjs                              1.64 MB        404.02 KB
15:29:24.005 | node_modules/@mui/material                              1.21 MB        376.59 KB
15:29:24.005 | node_modules/lambdafs/build                           818.19 KB        361.82 KB
15:29:24.005 | node_modules/caniuse-lite/data                        913.78 KB        327.31 KB
15:29:24.005 | node_modules/@tootallnate/quickjs-emscripten          722.71 KB        300.82 KB
15:29:24.006 | node_modules/@mui/base                                610.62 KB        180.35 KB
15:29:24.006 |  
15:29:24.006 | All dependencies                                       76.61 MB         50.55 MB
15:29:24.006 | Max serverless function size was exceeded for 2 functions
15:29:24.021 | Created all serverless functions in: 5.167s
15:29:24.060 | Collected static files (public/, static/, .next/static): 28.14ms
15:29:28.378 | Build Completed in /vercel/output [3m]
15:29:30.228 | Deploying outputs...
15:29:38.200 | Failed to process build result for "api/inngest/func_li_master". Data: {"type":"Lambda"}.
15:29:38.201 | Error: The Serverless Function "api/inngest/func_li_master" is 51.44mb which exceeds the maximum size limit of 50mb. Learn More: https://vercel.link/serverless-function-size

@teddis
Copy link

teddis commented Aug 18, 2023

Idea: is it possible to host just the chrome-aws-lambda on a separate hobby plan, say, with no other dependencies and then how would we connect our main server's puppeteer-core to that remote server?

@kettanaito
Copy link
Author

@teddis, to my best knowledge, it's Chrome's binary that exceeds the maximum allowed function size. If that's the case, you simply can't fit it into any project, empty or not. I wonder what changed though. If you pin dependencies, you will get the same binary size.

@david-matei
Copy link

You can not use version 14.x anymore...

Error: Node.js Version "14.x" is discontinued and must be upgraded. Please set "engines": { "node": "18.x" } in your `package.json` file to use Node.js 18. 

You can use version 16.x or higher.
But I still get this error:

Error: Failed to launch the browser process!
/tmp/chromium: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory
TROUBLESHOOTING: https://pptr.dev/troubleshooting
at Interface.onClose (file:///var/task/node_modules/@puppeteer/browsers/lib/esm/launch.js:262:24)
at Interface.emit (node:events:525:35)
at Interface.close (node:readline:590:8)
at Socket.onend (node:readline:280:10)
at Socket.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
at processTicksAndRejections (node:internal/process/task_queues:83:21)

Any help?

@RayRemnant
Copy link

RayRemnant commented Dec 8, 2023

For those seeking a solution for a free Vercel account with serverless function restrictions:

  1. Less than 50 MB;
  2. Less than 10 seconds of the execution time of function;

Here is my result after a few hours of testing and deployment:

Versions of chrome-aws-lambda and puppeteer-core:

package.json

{
  "chrome-aws-lambda": "6.0.0",
  "puppeteer-core": "6.0.0"
}

Node's version should be 14.x:

package.json

{
  "node": "14.x"
}

Also, set the same version in your vercel account.

Code of screenshot implementation:

import chromium from "chrome-aws-lambda";
import { NextApiRequest, NextApiResponse } from "next";

import fs from "fs";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse,
) {
  const executablePath = await chromium.executablePath;

  const randomId = Math.random().toString(36).substring(2, 15);

  const imagePath = `/tmp/screenshot-${randomId}.jpg`;

  const getScreenshot = async () => {
    console.log("Get screenshot", new Date());
    const browser = await chromium.puppeteer.launch({
      args: [
        ...chromium.args,
        "--autoplay-policy=user-gesture-required",
        "--disable-background-networking",
        "--disable-background-timer-throttling",
        "--disable-backgrounding-occluded-windows",
        "--disable-breakpad",
        "--disable-client-side-phishing-detection",
        "--disable-component-update",
        "--disable-default-apps",
        "--disable-dev-shm-usage",
        "--disable-domain-reliability",
        "--disable-extensions",
        "--disable-features=AudioServiceOutOfProcess",
        "--disable-hang-monitor",
        "--disable-ipc-flooding-protection",
        "--disable-notifications",
        "--disable-offer-store-unmasked-wallet-cards",
        "--disable-popup-blocking",
        "--disable-print-preview",
        "--disable-prompt-on-repost",
        "--disable-renderer-backgrounding",
        "--disable-setuid-sandbox",
        "--disable-speech-api",
        "--disable-sync",
        "--hide-scrollbars",
        "--ignore-gpu-blacklist",
        "--metrics-recording-only",
        "--mute-audio",
        "--no-default-browser-check",
        "--no-first-run",
        "--no-pings",
        "--no-sandbox",
        "--no-zygote",
        "--password-store=basic",
        "--use-gl=swiftshader",
        "--use-mock-keychain",
      ],
      executablePath,
      headless: true,
    });

    const page = await browser.newPage();

    try {
      await page.goto(req.body.url, { waitUntil: "networkidle2" });
    } catch (error) {
      console.log("Error", error);
    }

    await page.setViewport({ height: 1280, width: 1280 });

    await page.screenshot({
      path: imagePath,
    });

    await browser.close();
  };

  await getScreenshot();

  const img = fs.readFileSync(imagePath);

  res.writeHead(200, { "Content-Type": "image/jpeg" });
  res.end(img, "binary");
}

It's await chromium.executablePath(), as per the npm documentation . Hope that helps someone.

@rafayexalter
Copy link

Can anyone post the updated code with the correct versions again, please?

@taylor-lindores-reeves
Copy link

taylor-lindores-reeves commented Dec 17, 2023

Can anyone post the updated code with the correct versions again, please?

Hey man, I got it working like this:

get-browser.ts

import { env } from "@/env.mjs";
import chromium from "@sparticuz/chromium-min";
import puppeteer from "puppeteer-core";
import { IS_PRODUCTION } from "./constants";

export const getBrowser = async () => {
	if (IS_PRODUCTION) {
		return puppeteer.launch({
			args: [...chromium.args, "--hide-scrollbars", "--disable-web-security"],
			defaultViewport: chromium.defaultViewport,
			// you have to point to a Chromium tar file here 👇
			executablePath: await chromium.executablePath(
				"https://github.com/Sparticuz/chromium/releases/download/v116.0.0/chromium-v116.0.0-pack.tar",
			),
			headless: "new",
			ignoreHTTPSErrors: true,
		});
	}

	return puppeteer.connect({
		browserWSEndpoint: `wss://chrome.browserless.io?token=${env.BLESS_TOKEN}`,
	});
};

package.json

"@sparticuz/chromium-min": "^119.0.0",
"puppeteer": "10.1.0",
"puppeteer-core": "^21.6.1",

Works in production on Vercel, just incredibly slow compared to local. Looking for a better solution that is faster.

@kamalkech
Copy link

@taylor-lindores-reeves because u use browserless as wss and personaly i don't like this way, on general i see vercel, netlify still not very well for an advanced server app

@vikiival
Copy link

vikiival commented Jan 2, 2024

So if you are looking for a working version in 2024

TL;DR

  "dependencies": {
    "@sparticuz/chromium": "^119.0.2",
    "puppeteer-core": "^21.5.1"
  },
  "engines": {
    "node": "18"
  }

@tomsoderlund
Copy link

vikiival commented 2 weeks ago:

So if you are looking for a working version in 2024

TL;DR

Awesome @vikiival, but I’m getting:

Cannot find module 'follow-redirects'
Require stack:
- /var/task/node_modules/@sparticuz/chromium/build/index.js
- /var/task/app/controllers/helpers.js
- /var/task/app/controllers/api/dom.js
- /var/task/___now_launcher.js
- /var/runtime/index.mjs
Did you forget to add it to "dependencies" in `package.json`?

Did you encounter/solve this? I tried adding follow-redirects (yarn add follow-redirects) but doesn’t help.

@vikiival
Copy link

I do not use yarn 🤷

the main is deployed on Vercel
I am using node v18

@tomsoderlund
Copy link

I do not use yarn 🤷

the main is deployed on Vercel I am using node v18

@vikiival Have you ever gotten this error message?

The input directory "/var/task/node_modules/@sparticuz/chromium-min/bin" does not exist.

@kietn20
Copy link

kietn20 commented Jan 20, 2024

I do not use yarn 🤷
the main is deployed on Vercel I am using node v18

@vikiival Have you ever gotten this error message?

The input directory "/var/task/node_modules/@sparticuz/chromium-min/bin" does not exist.

I am in a similar situation.
👇 When I try the URL to chromium tar file, I get gateway timeout on vercel logs.
executablePath: await chromium.executablePath("https://github.com/Sparticuz/chromium/releases/download/v119.0.2/chromium-v119.0.2-pack.tar")

When I use await chromium.executablePath(), I get The input directory "/var/task/node_modules/@sparticuz/chromium-min/bin" does not exist.
Anyone know a solution?

@tomsoderlund
Copy link

@kietn20 You might want to check this conversation with @vikiival: vikiival/vercelgl#8

@tomsoderlund
Copy link

tomsoderlund commented Jan 22, 2024

So if you are looking for a working version in 2024

TL;DR

  "dependencies": {
    "@sparticuz/chromium": "^119.0.2",
    "puppeteer-core": "^21.5.1"
  },
  "engines": {
    "node": "18"
  }

I got @vikiival’s example to run eventually on Vercel.

Using pnpm as package manager seemed to make a difference.

And you need to increase maxDuration for the serverless functions in vercel.json:

"functions": {
  "api/**/*": {
    "maxDuration": 60
  }
}

@vikiival
Copy link

I got @vikiival’s example to run eventually on Vercel.

Glad that it works. Can I ask for for one star on the repo? 🥺

@Vansh1190
Copy link

Vansh1190 commented Feb 4, 2024

I got @vikiival’s example to run eventually on Vercel.

Glad that it works. Can I ask for for one star on the repo? 🥺

i am getting this, 504 Gateway Timeout
how i solve this ? also it is coming in your own vercel deployment

@vikiival
Copy link

vikiival commented Feb 4, 2024

i am getting this, 504 Gateway Timeout
how i solve this ?

Buy vercel premium :)

@tomsoderlund
Copy link

tomsoderlund commented Feb 4, 2024

i am getting this, 504 Gateway Timeout how i solve this ? also it is coming in your own vercel deployment

You need to increase the max duration for the serverless functions, see above https://gist.github.com/kettanaito/56861aff96e6debc575d522dd03e5725?permalink_comment_id=4843842#gistcomment-4843842

@vikiival
Copy link

vikiival commented Feb 4, 2024

i am getting this, 504 Gateway Timeout how i solve this ? also it is coming in your own vercel deployment

You need to increase the max duration for the serverless functions, see above https://gist.github.com/kettanaito/56861aff96e6debc575d522dd03e5725?permalink_comment_id=4843842#gistcomment-4843842

Added to the readme, thanks ❤️

vikiival/vercelgl@1ed08b4

@exchai93
Copy link

So if you are looking for a working version in 2024
TL;DR

  "dependencies": {
    "@sparticuz/chromium": "^119.0.2",
    "puppeteer-core": "^21.5.1"
  },
  "engines": {
    "node": "18"
  }

I got @vikiival’s example to run eventually on Vercel.

Using pnpm as package manager seemed to make a difference.

And you need to increase maxDuration for the serverless functions in vercel.json:

"functions": {
  "api/**/*": {
    "maxDuration": 60
  }
}

I've done all of the above, except specify the api route more specifically where I'm using puppeteer and chromium. I get an error to do with loaders:

./node_modules/.pnpm/puppeteer-core@21.11.0/node_modules/puppeteer-core/lib/esm/puppeteer/cdp/EmulationManager.js
--
19:15:50.724 | Module parse failed: Unexpected token (174:32)
19:15:50.724 | File was processed with these loaders:
19:15:50.724 | * ./node_modules/.pnpm/next@14.0.3_@babel+core@7.23.7_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/webpack/loaders/next-flight-loader/index.js
19:15:50.724 | * ./node_modules/.pnpm/next@14.0.3_@babel+core@7.23.7_react-dom@18.2.0_react@18.2.0/node_modules/next/dist/build/webpack/loaders/next-swc-loader.js
19:15:50.724 | You may need an additional loader to handle the result of these loaders.
19:15:50.724 | \|                 private: true,
19:15:50.724 | \|                 access: {
19:15:50.725 | >                     has: (obj)=>#applyViewport in obj,
19:15:50.725 | \|                     get: (obj)=>obj.#applyViewport
19:15:50.725 | \|                 },
19:15:50.725 |  
19:15:50.725 | Import trace for requested module:
19:15:50.725 | ./node_modules/.pnpm/puppeteer-core@21.11.0/node_modules/puppeteer-core/lib/esm/puppeteer/cdp/EmulationManager.js
19:15:50.725 | ./node_modules/.pnpm/puppeteer-core@21.11.0/node_modules/puppeteer-core/lib/esm/puppeteer/cdp/cdp.js
19:15:50.725 | ./node_modules/.pnpm/puppeteer-core@21.11.0/node_modules/puppeteer-core/lib/esm/puppeteer/puppeteer-core.js
19:15:50.725 | ./src/app/api/download-pdf/route.ts

Any ideas on what's going on here?

@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?

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