npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-template-pdf

v0.3.0

Published

Write PDF templates in React+TypeScript, compile to Handlebars, generate PDFs with Puppeteer

Readme

react-template-pdf

Write PDF templates in React + TypeScript during development. Compile to Handlebars + HTML at build time. Generate PDFs with Puppeteer at runtime.

Why?

  • Great DX: Write templates with React, TypeScript, Tailwind — autocomplete, type checking, component reuse
  • Fast in production: Zero React runtime — compiled templates are pure HTML + Handlebars
  • Lightweight: Only Handlebars + puppeteer-core in production, no heavy React bundle
  • Flexible: Works in any Node.js environment with Chromium available

Install

npm install react-template-pdf

Quick Start

1. Write a template

// templates/invoice.tsx
import { useTemplate, PageBreak, KeepTogether } from 'react-template-pdf'

type InvoiceData = {
  clientName: string
  total: number
  items: { name: string; price: number }[]
  hasDiscount: boolean
}

export function transform(data: InvoiceData) {
  return {
    ...data,
    formattedTotal: `$${data.total.toFixed(2)}`,
  }
}

export default function Invoice(props: ReturnType<typeof transform>) {
  const $ = useTemplate(props)

  return (
    <div className="p-8">
      <h1>Invoice for {$.clientName}</h1>

      <table>
        {$.items.$each((item) => (
          <tr>
            <td>{item.name}</td>
            <td>{item.price}</td>
          </tr>
        ))}
      </table>

      {$.hasDiscount.$if(
        () => <p>Discount applied!</p>,
        () => <p>No discount</p>,
      )}

      <p>Total: {$.formattedTotal}</p>
    </div>
  )
}

2. Build

npx rtpdf build ./templates

3. Generate PDFs in production

import { PdfEngine } from 'react-template-pdf/runtime'

const engine = new PdfEngine({
  chromiumPath: '/usr/bin/chromium',
  poolSize: 4,
})

await engine.start()

const pdf = await engine.generate('./dist/templates/invoice', {
  clientName: 'John Doe',
  total: 299.99,
  items: [
    { name: 'Widget', price: 199.99 },
    { name: 'Gadget', price: 100.00 },
  ],
  hasDiscount: true,
})

// pdf is a Buffer — save, send, stream, etc.

Template API

Proxy useTemplate

const $ = useTemplate(props)

// Simple values
{$.fieldName}

// Loops
{$.items.$each((item) => (
  <tr><td>{item.name}</td></tr>
))}

// Conditionals
{$.isActive.$if(
  () => <p>Active</p>,
  () => <p>Inactive</p>,
)}

Data Transformers

Export a transform function to process payload data before template rendering:

export function transform(data: RawInput) {
  return {
    ...data,
    formattedDate: new Date(data.date).toLocaleDateString(),
    total: data.items.reduce((sum, i) => sum + i.price, 0),
  }
}

Transforms run at runtime with real data — use them for formatting, calculations, and derived fields.

Components

| Component | Description | |-----------|-------------| | <PageBreak /> | Force a page break | | <KeepTogether> | Prevent page break inside a block | | asset(path) | Embed static images as base64 |

Smart Pagination

The package includes automatic pagination CSS that prevents awkward page breaks:

  • Table rows never break in the middle
  • Headings stay with their content
  • Figures stay together

Template Detection

Only .tsx files with an export default are compiled as templates. Files without a default export (helper components, utilities) are ignored by the build. This lets you organize templates in subfolders with shared components:

src/templates/
├── invoice/
│   ├── invoice.tsx        # Has export default → compiled as template
│   ├── Table.tsx           # No default export → ignored (helper component)
│   └── formatters.ts       # No default export → ignored (utility)
├── receipt/
│   └── receipt.tsx         # Has export default → compiled as template

CLI

# Build templates
npx rtpdf build ./src/templates
npx rtpdf build ./src/templates --out ./dist/pdf

# Validate templates (no hooks, no event handlers)
npx rtpdf validate ./src/templates

# Preview a template with mock data
npx rtpdf preview ./src/templates/invoice.tsx --data mock.json
npx rtpdf preview ./src/templates/invoice.tsx --data mock.json --out invoice.pdf

Runtime API

Simple (one-off)

import { generatePDF } from 'react-template-pdf/runtime'

const pdf = await generatePDF('./dist/templates/invoice', data, {
  chromiumPath: '/usr/bin/chromium',
})

Engine (high throughput)

import { PdfEngine } from 'react-template-pdf/runtime'

const engine = new PdfEngine({
  chromiumPath: '/usr/bin/chromium',
  poolSize: 4, // 4 parallel tabs
})

await engine.start()
const pdf = await engine.generate('./dist/templates/invoice', data)
await engine.stop()

PDF Options

await engine.generate(template, data, {
  format: 'A4',
  margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
  landscape: false,
  printBackground: true,
  headerTemplate: '<div>Header</div>',
  footerTemplate: '<div><span class="pageNumber"></span></div>',
  displayHeaderFooter: true,
})

How It Works

[DEV]                      [BUILD]                     [RUNTIME]
React + TypeScript  -->  HTML + Handlebars     -->  transform(payload)
  + Proxy ($)              (no React runtime)       + Handlebars fill
  + Tailwind               + CSS inline              + Puppeteer
  + Preview CLI            + images base64            --> PDF Buffer

License

MIT