Building a Next.js Invoice Generator with PDF Export

Step-by-step overview of building an invoice generator in Next.js and exporting invoices as PDFs using Puppeteer.

Invoices are a common requirement for SaaS apps, freelancing platforms, and internal tools. With Next.js, you can build a full-stack invoice generator that combines a React UI with server-side PDF rendering.

One practical approach is to use a simple HTML+CSS invoice template, then render it in a headless browser like Puppeteer and export it as a PDF. If you want to skip the implementation and use a ready-made solution, try the Invoice Generator tool on jsgenerator.com.

Why Choose Next.js + Puppeteer for PDF Export?

Next.js provides a unique environment where the frontend form and backend API coexist seamlessly. Combined with Puppeteer, it becomes a powerhouse for document generation.

Advantages of Server-Side Rendering

Generating PDFs on the server ensures data consistency, avoids client-side environment differences, and can handle sensitive information without exposing it to the client. This is crucial for financial documents and secure data management.

Precise Print Control with CSS

HTML and CSS (especially print styles) provide unparalleled layout control, allowing us to precisely match physical invoice styling requirements. You can use Tailwind CSS to style your React invoice generator components easily.

System Architecture Overview

A complete invoice generator typically follows this workflow:

1. **Frontend (React UI)**: A dynamic form for collecting invoice data (such as company info, items, tax rates, etc.).

2. **Backend (Next.js API Route)**: Receives the JSON data submitted from the frontend.

3. **Template Engine**: Injects the JSON data into a predefined HTML invoice template.

4. **PDF Generation (Puppeteer)**: Launches a headless browser instance, loads and renders the HTML, and exports it as a PDF file.

5. **File Delivery**: Streams the generated PDF back to the frontend for user download.

Step-by-Step Implementation Guide

Step 1: Create the Invoice Editing Form

We first create a page in Next.js containing a form for entering all invoice details. For secure data initialization, consider using our random string generator for unique invoice IDs.

// pages/invoice-creator.js
import { useState } from 'react';

export default function InvoiceCreator() {
  const [from, setFrom] = useState('');
  const [to, setTo] = useState('');
  const [items, setItems] = useState([{ description: '', quantity: 1, price: 0 }]);

  const addItem = () => {
    setItems([...items, { description: '', quantity: 1, price: 0 }]);
  };

  const generatePdf = async () => {
    // Send data to API route
    const response = await fetch('/api/generate-pdf', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ from, to, items }),
    });

    if (response.ok) {
      // Get PDF blob and trigger download
      const blob = await response.blob();
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.style.display = 'none';
      a.href = url;
      a.download = `invoice-${Date.now()}.pdf`;
      document.body.appendChild(a);
      a.click();
      window.URL.revokeObjectURL(url);
    } else {
      console.error('PDF generation failed');
    }
  };

  return (
    <div>
      <h1>Create Invoice</h1>
      {/* Detailed form controls here */}
      <button onClick={generatePdf}>Generate PDF Invoice</button>
    </div>
  );
}

Step 2: Design the HTML Invoice Template

This is the core part. We create a standalone HTML file (or a React component that returns JSX) as a template. It uses Tailwind CSS or plain CSS to define the invoice styling. You can also embed QR codes for payment links.

// templates/invoice-template.js
export const InvoiceTemplate = ({ data }) => {
  const total = data.items.reduce((sum, item) => sum + item.quantity * item.price, 0);
  
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <title>Invoice</title>
        <script src="https://cdn.tailwindcss.com"></script>
      </head>
      <body className="p-10">
        <div className="max-w-4xl mx-auto bg-white p-8 border border-gray-300">
          <h1 className="text-3xl font-bold mb-10">INVOICE</h1>
          <div className="flex justify-between mb-10">
            <div>
              <h2 className="font-bold">From:</h2>
              <p>{data.from}</p>
            </div>
            <div>
              <h2 className="font-bold">To:</h2>
              <p>{data.to}</p>
            </div>
          </div>
          <table className="w-full mb-10">
            <thead>
              <tr className="border-b-2 border-gray-300">
                <th className="text-left">Description</th>
                <th className="text-right">Qty</th>
                <th className="text-right">Price</th>
                <th className="text-right">Total</th>
              </tr>
            </thead>
            <tbody>
              {data.items.map((item, index) => (
                <tr key={index} className="border-b border-gray-200">
                  <td>{item.description}</td>
                  <td className="text-right">{item.quantity}</td>
                  <td className="text-right">${item.price.toFixed(2)}</td>
                  <td className="text-right">${(item.quantity * item.price).toFixed(2)}</td>
                </tr>
              ))}
            </tbody>
          </table>
          <div className="text-right text-xl font-bold">
            Total: ${total.toFixed(2)}
          </div>
        </div>
      </body>
    </html>
  );
};

Step 3: Implement the PDF Generation API Route

In the Next.js API route, we integrate Puppeteer to render the template and generate the PDF.

// pages/api/generate-pdf.js
import { InvoiceTemplate } from '../../templates/invoice-template';
import ReactDOMServer from 'react-dom/server';
import puppeteer from 'puppeteer-core';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).end();
  }

  const { from, to, items } = req.body;

  try {
    // 1. Render the React template to an HTML string
    const htmlContent = ReactDOMServer.renderToStaticMarkup(
      InvoiceTemplate({ data: { from, to, items } })
    );

    // 2. Launch Puppeteer
    const browser = await puppeteer.launch({
      executablePath: process.env.CHROME_PATH,
      args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });
    const page = await browser.newPage();

    // 3. Set HTML content and wait for all resources to load
    await page.setContent(htmlContent, { waitUntil: 'networkidle0' });

    // 4. Generate PDF
    const pdfBuffer = await page.pdf({
      format: 'A4',
      printBackground: true,
      margin: { top: '1cm', right: '1cm', bottom: '1cm', left: '1cm' },
    });

    await browser.close();

    // 5. Return the PDF to the client
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="invoice.pdf"');
    res.send(pdfBuffer);

  } catch (error) {
    console.error('PDF generation error:', error);
    res.status(500).json({ error: 'Failed to generate PDF' });
  }
}

Production Environment Considerations

Chrome Management in Serverless

In Serverless environments like Vercel, launching Chrome directly may not be feasible. Solutions include using adapters like `@sparticuz/chromium` or connecting to a standalone Chrome rendering service.

Performance and Security

Launching a browser instance has significant overhead. Consider reusing browser instances or using connection pools. Also, strictly validate and sanitize user input to prevent HTML injection attacks.

Conclusion

As demonstrated by the Invoice Generator on jsgenerator.com, a production-ready solution requires intuitive UI and optimized output. For developers who want to quickly build stable features, leveraging professional tools and understanding JavaScript random number generation is essential.

Frequently Asked Questions

How to export PDF in Next.js?

The most reliable way is using Puppeteer on the server to render HTML/CSS and export it as an A4 PDF buffer.

Is Puppeteer good for invoice generation?

Yes, Puppeteer allows full control over the layout using standard web technologies, ensuring high-quality, pixel-perfect invoices.

Can I use Tailwind CSS with Puppeteer?

Absolutely. You can inject Tailwind's CDN or pre-compiled CSS into your HTML template before rendering.