DEV Community

Cover image for Creating a Next.js API to Convert HTML to PDF with Puppeteer (Vercel-Compatible)
HARSH VATS
HARSH VATS

Posted on

Creating a Next.js API to Convert HTML to PDF with Puppeteer (Vercel-Compatible)

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:

  1. Puppeteer requires a Chromium binary, which exceeds Vercel's size limits.
  2. 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:

  1. Use a minimal Chromium build designed for serverless environments.
  2. Configure Puppeteer to use this minimal Chromium version.
  3. 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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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" }
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  });
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Extract the HTML from the request body.
  2. Launch a browser instance using the getBrowser function.
  3. Create a new page and set its content to the provided HTML.
  4. Generate a PDF from the page content.
  5. Close the browser to free up resources.
  6. 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)
}
Enter fullscreen mode Exit fullscreen mode

Deployment Considerations

When deploying to Vercel, keep these points in mind:

  1. 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.

  2. Memory Usage: Be mindful of memory usage. The minimal Chromium binary helps, but complex PDFs might still use significant memory.

  3. 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.

  4. Error Handling: Implement robust error handling to manage timeouts or resource constraints.

  5. 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)