Blog/Comparisons

React PDF vs Puppeteer: Generate the Same Report Both Ways

Compare React PDF and Puppeteer using a shared 72-row report, downloadable PDFs, pinned dependencies, and reproducible local measurements.

Comparisons··4 min read

React PDF and Puppeteer both generate PDFs, but they accept different document descriptions. @react-pdf/renderer uses PDF-oriented React components. Puppeteer prints an HTML document through Chromium. Choosing between them starts with the layout system your application can maintain.

We implemented the same 72-row service report in both, using the same item descriptions, quantities, and total. The source and both PDFs are downloadable. This is a small local experiment with disclosed differences, not a universal engine ranking. Download the example projects, fixtures, and results.

What the example holds constant

Both documents use A4 paper, approximately 18mm margins, a title, a month label, 72 rows, and a grand total of USD 900.00. Neither implementation fetches network images or loads application JavaScript. Each row is short enough to fit on a page.

The layouts are authored separately. Chromium uses the HTML document's Arial/system-font stack; React PDF uses its Helvetica font. The table column sizing and header behavior also differ. Those choices affect file size, wrapping, and visual appearance, so do not interpret the measurements as isolated tests of identical typography.

The npm lockfile records exact dependencies, including Puppeteer Core 25.10.0 and React PDF 4.9.0. The local run used Node 26.7.0 and Chromium 151.0.7922.173 on Linux.

The HTML implementation

The HTML uses a real table, a repeating thead, and break-inside: avoid on rows. Puppeteer loads that HTML, waits for fonts, and prints it:

const page = await browser.newPage();
try {
  await page.setContent(html, { waitUntil: "load" });
  await page.evaluate(() => document.fonts.ready);
  await page.pdf({
    path: "report.pdf",
    preferCSSPageSize: true,
    printBackground: true
  });
} finally {
  await page.close();
}

Print settings and their interactions are documented in Puppeteer's PDF options. In this example, the page size comes from @page; the browser does not infer your intended paper size from the screen layout.

The React PDF implementation

The React version uses Document, Page, View, and Text. It gives each row explicit columns and disables wrapping within that row. It uses React.createElement so the downloaded script runs directly in Node without a JSX build step.

React PDF supports its own documented styling properties and pagination features. Consult its styling reference and page-wrapping documentation before assuming that a browser stylesheet can be reused unchanged.

The sample's column header appears once. The HTML version relies on the table header repeating across pages. A fixed header in React PDF requires a deliberate layout choice; adding one should reserve space and be checked for overlap. The two supplied PDFs make that difference inspectable.

Recorded local measurements

We performed one warm-up followed by five sequential measured renders per engine. Chromium startup was excluded; opening and closing a new page was included. Times include writing the output file. There were no hosted API requests in this comparison.

ImplementationMedian local renderFile sizePages
Puppeteer with an existing Chromium process49.1 ms96,760 bytes3
React PDF22.6 ms7,781 bytes3

For this fixture and implementation, React PDF rendered faster and produced a smaller file. These results do not establish production throughput, cold-start behavior, memory use, accessibility, or visual superiority. Font embedding and PDF structure can strongly affect file size.

Inspect the Puppeteer PDF, React PDF document, and all timing samples. A hosted service includes network and scheduling time that this local comparison does not measure.

Reproduce and extend the experiment

Install Node 22 or newer, Chromium, and Poppler. Set CHROMIUM_PATH when Chromium is not at /usr/bin/chromium. From the archive root:

npm ci
npm run fixtures
npm run compare
npm test

The regression check compares a separately generated clean Chromium report with the fixture and verifies that a deliberately enlarged table fails. It is not a cross-engine pixel-equality test.

Before choosing an engine, replace the sample rows with your actual difficult cases: an unusually long description, a chart, a custom font, international text, and a table spanning many pages. Add the repeating header behavior your application needs to both implementations. Measure cold and warm runs separately if you intend to deploy on short-lived functions.

Choose according to the document you maintain

React PDF is a useful fit when you want dedicated React document components and explicit PDF layout rules. Puppeteer is useful when you already own HTML/CSS templates or need browser rendering behavior. RelayPDF's HTML-to-PDF API offers a managed route for that HTML workflow; it is not measured by the local timings above.

For the operational tradeoffs of hosting Chromium, see Puppeteer versus a hosted API. For a broader set of fixtures, use the open rendering test suite.

Ready to generate?

One API for HTML, Markdown, URLs, and Office. REST, SDK, CLI, or MCP.