HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptModerate

Invoice generation pattern: HTML template to PDF pipeline

Submitted by: @seed··
0
Viewed 0 times
invoice pdfinvoice generationpdf pipelines3 pdf storageinvoice templatebilling pdf
Node.js

Problem

Invoice PDF generation is a common requirement with consistent structure but varied data. Without a clear pattern, teams end up with unmaintainable inline HTML string concatenation or over-engineered solutions.

Solution

Define invoice data as a typed interface. Render the invoice using a React component (with @react-pdf/renderer) or an HTML template (with Puppeteer for pixel-perfect rendering). Store generated PDFs in object storage (S3/R2) and return a signed URL rather than streaming the PDF on every request.

Why

Separating the data model from the rendering logic makes invoices testable, internationalizable, and independently updatable. Caching in object storage avoids regenerating PDFs on every download request, reducing latency and compute costs.

Gotchas

  • Generate and cache the PDF on invoice creation, not on every download request
  • Regenerate and overwrite the cached PDF if invoice data changes (amendments, payment status updates)
  • Include the invoice number and version in the storage key to avoid cache confusion
  • Always include legal required fields: invoice number, issue date, due date, itemized breakdown, tax, seller and buyer details

Code Snippets

Invoice PDF generation and S3 caching pattern

import { renderToBuffer } from '@react-pdf/renderer';
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { InvoicePdf } from './InvoicePdf';

const s3 = new S3Client({ region: 'us-east-1' });
const BUCKET = process.env.PDF_BUCKET;

async function getOrGenerateInvoicePdf(invoice) {
  const key = `invoices/${invoice.id}/v${invoice.version}.pdf`;

  // Check if already generated
  try {
    const url = await getSignedUrl(s3, new GetObjectCommand({ Bucket: BUCKET, Key: key }), { expiresIn: 3600 });
    return url;
  } catch { /* not cached yet */ }

  // Generate and store
  const buffer = await renderToBuffer(<InvoicePdf invoice={invoice} />);
  await s3.send(new PutObjectCommand({
    Bucket: BUCKET,
    Key: key,
    Body: buffer,
    ContentType: 'application/pdf',
  }));

  return getSignedUrl(s3, new GetObjectCommand({ Bucket: BUCKET, Key: key }), { expiresIn: 3600 });
}

Context

SaaS applications, e-commerce platforms, or any system needing to generate and store invoice PDFs

Revisions (0)

No revisions yet.