Dynamic PDF Generation: Engineering the Digital Paper
How to generate high-fidelity PDFs (Invoices, Tickets) using React and Node.js. A comparison of @react-pdf/renderer vs Puppeteer in Serverless environments.
The world runs on HTML, but business runs on PDF.
An e-commerce order is not “real” until there is an Invoice. A concert ticket is not “valid” until there is a QR code on a PDF.
For years, generating PDFs was the most hated task in backend development. It involved archaic tools like wkhtmltopdf, XML templates, or fragile Python scripts.
At Maison Code Paris, we bring PDF generation into the modern era. We use the same component architecture (React) for our documents as we do for our user interfaces. This ensures that the Brand Consistency is absolute. The font on the website is the font on the invoice.
Why Maison Code Discusses This
At Maison Code Paris, we act as the architectural conscience for our clients. We often inherit “modern” stacks that were built without a foundational understanding of scale. We see simple APIs that take 4 seconds to respond because of N+1 query problems, and “Microservices” that cost $5,000/month in idle cloud fees.
We discuss this topic because it represents a critical pivot point in engineering maturity. Implementing this correctly differentiates a fragile MVP from a resilient, enterprise-grade platform that can handle Black Friday traffic without breaking a sweat.
The Landscape: How to render a PDF
There are three main ways to generate a PDF today, each with massive trade-offs.
| Approach | Tooling | Pros | Cons |
|---|---|---|---|
| Browserless (Headless Chrome) | Puppeteer, Playwright | Pixel-perfect CSS support. If you can build it in HTML, you can print it. | Slow. Heavy memory usage. Requires launching a browser instance (Chromium) which is painful in Serverless. |
| PDF Primitives | PDFKit, jsPDF | Extremely fast. Zero dependencies. | Developer H*. You have to draw lines manually (doc.moveTo(100, 100).lineTo(200, 200)). Maintaining layouts is a nightmare. |
| React-PDF / @react-pdf/renderer | React, Flexbox | The Holy Grail. Write React components, get a PDF. Fast enough. | CSS subset only (Flexbox mainly). No Grid support yet. |
At Maison Code, we strictly standardize on React-PDF.
The Architecture: Why React-PDF Wins
The mental model shift is simple: A PDF is just another Render Target.
Just like ReactDOM renders to the DOM, and ReactNative renders to iOS/Android views, @react-pdf/renderer renders to the PDF Specification.
1. Code Sharing
You can share your Design System tokens types.
import { StyleSheet } from '@react-pdf/renderer';
import { theme } from '@/design-system/theme';
const styles = StyleSheet.create({
header: {
fontFamily: theme.fonts.heading, // "Bodoni Moda"
color: theme.colors.void, // #000000
fontSize: 24,
}
});
This guarantees that your Invoice matches your Checkout page pixel-for-pixel.
2. Serverless Cold Starts
Running Puppeteer on AWS Lambda is a constant battle against the 50MB package limit and 10-second cold starts. React-PDF is just a Node.js library. It effectively adds 0ms to your cold start. It compiles strings to binary buffers. It is CPU bound, not I/O bound.
Implementation: The “Digital Paper” Component
Here is a real-world example of an Invoice component.
import { Document, Page, Text, View, StyleSheet, Font } from '@react-pdf/renderer';
// Register Custom Fonts (Critical for branding)
Font.register({
family: 'Bodoni Moda',
src: 'https://cdn.maisoncode.paris/fonts/BodoniModa-Regular.ttf'
});
const Invoice = ({ order }) => (
<Document>
<Page size="A4" style={styles.page}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.brand}>MAISON CODE.</Text>
<Text style={styles.invoiceId}>INV-{order.id}</Text>
</View>
{/* Line Items */}
<View style={styles.table}>
{order.items.map(item => (
<View style={styles.row}>
<Text style={styles.cellDesc}>{item.name}</Text>
<Text style={styles.cellQty}>x{item.quantity}</Text>
<Text style={styles.cellTotal}>${item.price}</Text>
</View>
))}
</View>
{/* Footer */}
<View style={styles.footer}>
<Text>Thank you for your business. This document is a digital original.</Text>
</View>
</Page>
</Document>
);
The API Endpoint
You don’t save files to disk. You stream them directly to the client or S3.
// Next.js / Node.js API Route
import { renderToStream } from '@react-pdf/renderer';
export async function POST(req) {
const stream = await renderToStream(<Invoice order={req.body} />);
return new Response(stream, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="invoice.pdf"'
}
});
}
Performance: The 500ms Rule
In E-commerce, if the “Download Invoice” button spins for 5 seconds, the user assumes the site is broken. Puppeteer takes 3-8 seconds to boot, navigate, and print. React-PDF takes 200ms-600ms to render a complex invoice.
This speed difference allows you to generate PDFs Synchronously On-Demand, rather than queuing them in a background job (Celery/SQS) and emailing them later. One less moving part. One less queue to monitor.
Conclusion
Stop treating PDFs as second-class citizens. By adopting React-PDF, you bring the Document Layer into your standard engineering workflow.
- Version Control: Your invoice templates are git-tracked.
- Type Safety: Your PDF generation fails at build time if you miss a
pricefield. - Design Consitency: Your brand stays premium, even on paper.
In the luxury sector, the post-purchase experience is as important as the sale. A beautiful, instant invoice is the final touch of a premium transaction.
Hire our Architects to audit your document generation pipeline.