patternjavascriptModerate
Invoice generation pattern: HTML template to PDF pipeline
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.