
PDF Without Worries
Comparing two approaches to programmatic PDF generation in Node.js: react-pdf for React-native document composition, and Puppeteer for full-browser HTML-to-PDF conversion.
Generating PDFs programmatically is a common requirement — reports, invoices, exports. But picking the right tool matters. Here is what we looked for:
- Styling (fonts, colors, etc.)
- Tables with a repeating header on every page
- Header/footer on every page with page numbers
- Running custom code (for charts, maps, etc.)
We evaluated two approaches: react-pdf and Puppeteer.
React-pdf
React-pdf combines React for layout definition with PDFKit for rendering. It supports both client-side and server-side rendering without relying on HTML-to-PDF conversion.
Key features:
- CSS and flexbox-based styling
- Simple component-based approach familiar to React developers
- Metadata support and live preview capability
Server-side rendering example:
const { default: ReactPDF } = require("@react-pdf/renderer");
ReactPDF.render(<OneLinePDF />, `react-pdf.pdf`);Tables in react-pdf
React-pdf has no native table component. Tables are built by styling View and Text elements with flexbox. A basic table looks like this:

A critical issue: table rows can break across pages. The fix is to add wrap={false} to rows you want to keep together:

Repeating table headers are not supported natively and require manual row counting to replicate the header at page boundaries.
Header / Footer
Achieved via fixed View elements with absolute positioning. Page numbers are accessed through render functions that receive pageNumber as a parameter.
Custom Code Limitation
Canvas support exists, but integration with external charting or mapping libraries is impractical. Google Maps integration proved particularly problematic.
Live Preview
One unique advantage: react-pdf supports a live in-browser preview of the document as you edit.

Puppeteer
Puppeteer controls a headless Chrome instance via Node.js, enabling HTML-to-PDF conversion with the full power of a real browser.
Basic implementation:
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html);
await page.pdf({ path: "puppeteer.pdf" });
await browser.close();Tables in Puppeteer
Native HTML <table> support means table headers (<thead>) repeat automatically across pages. Cell breaking is prevented automatically.


Header / Footer
Template-based approach using headerTemplate and footerTemplate parameters with inline CSS. Page numbers are injected via special class-based placeholders (pageNumber, totalPages).

Custom Code
Full JavaScript engine access enables charting libraries, Google Maps rendering, and any other dynamic content — a significant advantage over react-pdf.
Limitations
- No document metadata support
- No document encryption or editable form creation
- Resource-intensive (spawns a full Chrome process)
- Can be tricky to deploy on serverless platforms like AWS Lambda
Conclusion
Neither solution is universally better — it depends on your project's requirements:
| react-pdf | Puppeteer | |
|---|---|---|
| Client-side rendering | Yes | No |
| Metadata support | Yes | No |
| Native HTML tables | No | Yes |
| Repeating table headers | Manual workaround | Automatic |
| Custom code (charts, maps) | Limited | Full |
| Cloud deployment | Easy | Challenging |
react-pdf excels for simple, self-contained documents where React integration matters. Puppeteer wins for flexibility, especially when your requirements are still evolving — its HTML foundation means you can swap rendering strategies without rewriting templates.