How to Use Puppeteer on Vercel with Next.js
Introduction
In this tutorial, we will build a Next.js application that generates PDF files using the Headless Chrome API Puppeteer. Puppeteer will be utilized as a serverless function, and the app will be deployed on Vercel.
Table of Contents
- Project Setup
- Puppeteer
- Deployment
Project Setup
First, let's create a new Next.js application. For simplicity, we will use JavaScript. Open your terminal and run the following command:
npx create-next-app@latest
You should receive a similar output to this. In my case, I named the project next-puppeteer-demo-app
:
npx create-next-app@latest
Need to install the following packages:
create-next-app@latest
Ok to proceed? (y) y
What is your project named? ... next-puppeteer-demo-app
Navigate to your project directory and run:
npm run dev
This command will start the application, and you should be able to view it in your browser using the address localhost:3000
.
We will remove the Home.module.css
file located inside the styles folder. Additionally, we will remove some content inside pages/index.js
. So that it looks like this:
export default function Home() {
return (
<div>
<main>{/* content will be placed here */}</main>
</div>
);
}
Styling the web application is not the focus of this tutorial.
Now rename the hello.js
file located in pages/api
to generatePdf.js
.
Files inside pages/api
function as your backend; each file is essentially a serverless function. Later on, we will call this function from our frontend, triggering a serverless function that generates a PDF file.
Your generatePdf.js
should look like this for now:
export default function handler(req, res) {
res.status(200).json({ message: "Generate PDF!" });
}
Puppeteer
We are going to install puppeteer via npm, but we are not going to install the full puppeteer package since that won't work. The maximum size for a Serverless Function is 50 MB, by using the puppeteer package we exceed that size which causes an error.
We are going to install chrome-aws-lambda which is a Chromium Binary for AWS Lambda and Google Cloud Functions
npm install chrome-aws-lambda --save-prod
npm install playwright-core
Next, update your generatePdf.js
file. If you want to test the application locally, you need to install Chromium on your local development machine and pass the location to the ternary operation.
On macOS, you can install Chromium using Homebrew:
brew install --cask chromium
Your updated generatePdf.js
should look like this:
import chromium from "chrome-aws-lambda";
import playwright from "playwright-core";
export default async function generatePdf(req, res) {
try {
const browser = await playwright.chromium.launch({
args: [...chromium.args, "--font-render-hinting=none"], // This way fix rendering issues with specific fonts
executablePath:
process.env.NODE_ENV === "production"
? await chromium.executablePath
: "/usr/local/bin/chromium",
headless:
process.env.NODE_ENV === "production" ? chromium.headless : true,
});
const context = await browser.newContext();
const page = await context.newPage();
// This is the path of the url which shall be converted to a pdf file
const pdfUrl =
process.env.NODE_ENV === "production"
? "https://your.app/pdf"
: "http://localhost:3000/pdf";
await page.goto(pdfUrl, {
waitUntil: "load",
});
const pdf = await page.pdf({
path: "/tmp/awesome.pdf", // we need to move the pdf to the tmp folder otherwise it won't work properly
printBackground: true,
format: "a4",
});
await browser.close();
return res.status(200).json({ pdf });
} catch (error) {
return res.status(error.statusCode || 500).json({ error: error.message });
}
}
This method starts Chromium with specific arguments. Once Chromium is started, it navigates to the specified URL and waits until it's fully loaded. Then, it converts the page to a PDF file, saving it in a temporary file location. Finally, it closes the browser and returns the generated PDF file.
Now, update your frontend. Create a new file named pdf.js
inside the pages directory. In this file, you can create a custom view. In our case, we'll simply return a string, but you can style it as desired:
const Pdf = () => {
return (
<div>
<h1>This page is going to be converted to a pdf, wow!</h1>
</div>
);
};
export default Pdf;
Next, open the index.js
file and add a button that will call our backend method. We'll use fetch to call the API:
export default function Home() {
const generatePdf = async () => {
const res = await fetch("/api/generatePdf", {
headers: {
"Content-Type": "application/json",
},
method: "POST", // You probably can use get aswell, you can use a post request with a custom body to generate dynamic data in your pdf view, I am going to cover that in a different post :)
});
return res.json();
};
return (
<div>
<main>
<button type="button" onClick={generatePdf}>
Create PDF
</button>
</main>
</div>
);
}
Now you can open your application locally and click the button to generate a PDF file. The file will be automatically downloaded by your browser.
Deployment
We will deploy the application on Vercel. Visit vercel.com, register an account or sign in to an existing account, and create a git repository. Push your project to that repository (you can use GitHub or GitLab).
Once you've pushed your code, go to vercel.com, click on "New Project," search for your project, and import it. Skip the "Create a Team" section and click on "Deploy."
The deployment/build process should take only a couple of minutes. Once the project is successfully deployed, you can test it using the production URL provided by Vercel (yourawesomeapp.vercel.app)
.
Cheers!