Converting HTML to PDF is a common requirement in web applications. In this blog post, we'll explore how to create a Next.js API route that converts HTML to PDF using Puppeteer, and we'll ensure it works when deployed to Vercel.
The Challenge
While Puppeteer is a powerful tool for HTML to PDF conversion, it presents challenges when deploying to serverless environments like Vercel. The main issues are:
- Puppeteer requires a Chromium binary, which exceeds Vercel's size limits.
- Serverless functions have limited execution time and resources.
The Solution
We'll use a combination of @sparticuz/chromium-min
and puppeteer-core
to overcome these limitations. Here's how we'll approach it:
- Use a minimal Chromium build designed for serverless environments.
- Configure Puppeteer to use this minimal Chromium version.
- Optimize the PDF generation process for serverless execution.
Step 1: Setting Up the Project
First, create a new Next.js project or use an existing one. Then, install the necessary dependencies:
npm install @sparticuz/chromium-min puppeteer-core
To ensure compatibility and optimal performance, it's important to use the correct versions of the required packages. As of the latest testing, the following versions are recommended:
{
"dependencies": {
"@sparticuz/chromium-min": "^129.0.0",
"puppeteer-core": "^23.5.0"
}
}
Step 2: Creating the API Route
Create a new file at app/api/html-to-pdf/route.js
(for Next.js 13+ app router) or pages/api/html-to-pdf.js
(for Pages router). Here's the code:
const chromium = require("@sparticuz/chromium-min");
const puppeteer = require("puppeteer-core");
async function getBrowser() {
return puppeteer.launch({
args: [...chromium.args, "--hide-scrollbars", "--disable-web-security"],
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(
`https://github.com/Sparticuz/chromium/releases/download/v129.0.0/chromium-v129.0.0-pack.tar`
),
headless: chromium.headless,
ignoreHTTPSErrors: true
});
}
export async function POST(request) {
try {
const { html } = await request.json();
const browser = await getBrowser();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle0" });
const pdfBuffer = await page.pdf({
format: "A4",
printBackground: true,
margin: { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" }
});
await browser.close();
return new Response(pdfBuffer, {
headers: {
"Content-Type": "application/pdf",
"Content-Disposition": 'attachment; filename="output.pdf"'
}
});
} catch (error) {
console.error("Error generating PDF:", error);
return new Response(JSON.stringify({ error: "Failed to generate PDF" }), {
status: 500,
headers: { "Content-Type": "application/json" }
});
}
}
Step 3: Understanding the Code
Let's break down the key parts of this code:
Browser Configuration
The getBrowser
function sets up Puppeteer with the minimal Chromium binary:
async function getBrowser() {
return puppeteer.launch({
args: [...chromium.args, "--hide-scrollbars", "--disable-web-security"],
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(
`https://github.com/Sparticuz/chromium/releases/download/v129.0.0/chromium-v129.0.0-pack.tar`
),
headless: chromium.headless,
ignoreHTTPSErrors: true
});
}
This configuration uses the @sparticuz/chromium-min
package to provide a minimal Chromium binary compatible with serverless environments.
PDF Generation
The main logic for PDF generation is in the POST
function:
- Extract the HTML from the request body.
- Launch a browser instance using the
getBrowser
function. - Create a new page and set its content to the provided HTML.
- Generate a PDF from the page content.
- Close the browser to free up resources.
- Return the PDF as a response with appropriate headers.
Step 4: Using the API
To use this API, send a POST request with the HTML content in the request body:
const response = await fetch('/api/html-to-pdf', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ html: '<h1>Hello, World!</h1>' }),
});
if (response.ok) {
const blob = await response.blob();
// Handle the PDF blob (e.g., download or display)
}
Deployment Considerations
When deploying to Vercel, keep these points in mind:
Execution Time: Vercel has a maximum execution time of 10 seconds for hobby plans and 60 seconds for pro plans. Optimize your HTML and PDF generation process to fit within these limits.
Memory Usage: Be mindful of memory usage. The minimal Chromium binary helps, but complex PDFs might still use significant memory.
Cold Starts: Serverless functions can experience cold starts. The first invocation might be slower as it needs to download and set up the Chromium binary.
Error Handling: Implement robust error handling to manage timeouts or resource constraints.
Caching: Consider implementing caching strategies for frequently generated PDFs to reduce load on your serverless functions.
Conclusion
This approach allows you to create a powerful HTML to PDF conversion API using Next.js and Puppeteer, compatible with Vercel's serverless environment. By leveraging @sparticuz/chromium-min
and puppeteer-core
, we overcome the main challenges of running Puppeteer in a serverless context.
Top comments (0)