Engineering blogNext.jsPDF generation

Convert HTML to PDF in Next.js – Complete Guide

Build a production-ready PDF generation API in Next.js using API routes and Puppeteer. This guide covers server-side rendering, error handling, and deployment considerations for Next.js applications.

ER

Evan Rivers

Founder, HTML2PDFConverter

Updated · Dec 20257 min read

Tutorial overview

We'll build a Next.js API route that converts HTML to PDF, handle React component rendering, and explore deployment strategies for serverless and traditional hosting.

Next.js makes it straightforward to generate PDFs from HTML on the server. Whether you're building invoice generators, report exporters, or document creators, you can leverage Next.js API routes with Puppeteer to create production-ready PDF endpoints. In this guide, we'll show you how to convert HTML to PDF in Next.js, covering everything from basic setup to advanced server-side rendering techniques.

Why Use Next.js for PDF Generation?

Next.js offers several advantages for PDF generation compared to standalone Node.js scripts:

  • API Routes: Built-in serverless functions that handle HTTP requests without additional Express setup.
  • React SSR: Render React components server-side and convert them directly to PDF with full styling support.
  • Type Safety: TypeScript support out of the box for better developer experience and fewer runtime errors.
  • Deployment: Easy deployment to Vercel, AWS, or any Node.js hosting with built-in optimizations.

Prerequisites

Before you start, make sure you have:

  • Next.js project set up (v13+ with App Router or Pages Router)
  • Node.js installed (v18+ recommended)
  • Basic knowledge of React and Next.js API routes
  • Understanding of async/await and server-side rendering

Step 1: Install Dependencies

First, install Puppeteer in your Next.js project. Puppeteer will handle the HTML-to-PDF conversion:

npm install puppeteer

Note: Puppeteer downloads Chromium automatically. For production deployments, especially on serverless platforms, consider using puppeteer-core with a managed Chrome instance to reduce bundle size.

Step 2: Create an API Route

Create a new API route in your Next.js project. For the App Router (Next.js 13+), create app/api/pdf/route.ts. For Pages Router, create pages/api/pdf.ts:

import { NextRequest, NextResponse } from 'next/server';
import puppeteer from 'puppeteer';

export async function POST(request: NextRequest) {
  try {
    const { html, url } = await request.json();
    
    if (!html && !url) {
      return NextResponse.json(
        { error: 'Either html or url must be provided' },
        { status: 400 }
      );
    }

    const browser = await puppeteer.launch({
      headless: true,
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
    
    const page = await browser.newPage();
    
    if (url) {
      await page.goto(url, { waitUntil: 'networkidle0' });
    } else {
      await page.setContent(html, { waitUntil: 'networkidle0' });
    }
    
    const pdf = await page.pdf({
      format: 'A4',
      printBackground: true,
      margin: {
        top: '20px',
        right: '20px',
        bottom: '20px',
        left: '20px',
      },
    });
    
    await browser.close();
    
    return new NextResponse(pdf, {
      headers: {
        'Content-Type': 'application/pdf',
        'Content-Disposition': 'attachment; filename="document.pdf"',
      },
    });
  } catch (error) {
    console.error('PDF generation error:', error);
    return NextResponse.json(
      { error: 'Failed to generate PDF' },
      { status: 500 }
    );
  }
}

Step 3: Render React Components to PDF

One of Next.js's strengths is server-side rendering. You can render React components directly to HTML and convert them to PDF. Here's how to create a reusable PDF generation utility:

import { renderToString } from 'react-dom/server';
import puppeteer from 'puppeteer';

export async function generatePDFFromComponent(Component: React.ComponentType<any>, props: any) {
  // Render React component to HTML string
  const htmlString = renderToString(<Component {...props} />);
  
  // Wrap in a complete HTML document with styles
  const fullHTML = `<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <style>
      body { font-family: system-ui, sans-serif; }
      /* Add your component styles here */
    </style>
  </head>
  <body>${htmlString}</body>
</html>`;

  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });
  
  const page = await browser.newPage();
  await page.setContent(fullHTML, { waitUntil: 'networkidle0' });
  
  const pdf = await page.pdf({
    format: 'A4',
    printBackground: true,
  });
  
  await browser.close();
  return pdf;
}

Step 4: Handle GET Requests (Optional)

You can also create a GET endpoint that accepts a URL query parameter for quick PDF generation:

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const url = searchParams.get('url');
  
  if (!url) {
    return NextResponse.json(
      { error: 'URL parameter is required' },
      { status: 400 }
    );
  }
  
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });
  
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'networkidle0' });
  
  const pdf = await page.pdf({
    format: 'A4',
    printBackground: true,
  });
  
  await browser.close();
  
  return new NextResponse(pdf, {
    headers: {
      'Content-Type': 'application/pdf',
    },
  });
}

Step 5: Deployment Considerations

When deploying Next.js PDF generation to production, especially on serverless platforms like Vercel, consider these optimizations:

  • Chrome Binary: Use external Chrome instances or managed services to avoid large bundle sizes.
  • Timeout Limits: Be aware of serverless function timeout limits (typically 10-60 seconds).
  • Memory Limits: Puppeteer can be memory-intensive; monitor your function's memory usage.
  • Caching: Consider caching generated PDFs for frequently requested documents.

Common Issues & Solutions

Here are common challenges when generating PDFs in Next.js and how to solve them:

IssueSolution
Function timeout on VercelMove PDF generation to a background job using a queue system, or upgrade to a Pro plan for longer timeouts. Consider using Edge Functions for lighter operations.
Puppeteer bundle too largeUse puppeteer-core and connect to an external Chrome instance, or use a managed PDF service.
React styles not renderingEnsure CSS is inlined or loaded via <style> tags. External stylesheets may not load in headless Chrome.
Memory errors in productionAlways close the browser instance, limit concurrent PDF generations, and consider using a dedicated PDF service for high-volume applications.

Conclusion

You now know how to convert HTML to PDF in Next.js. By leveraging API routes and Puppeteer, you can build robust PDF generation endpoints that integrate seamlessly with your Next.js application. Remember to handle errors gracefully, optimize for your deployment platform, and consider caching strategies for better performance.

For a managed solution that handles infrastructure, scaling, and optimization automatically, check out HTML2PDFConverter. It provides a simple API for PDF generation without managing Puppeteer deployments or Chrome binaries.