Skip to content

ComputecOrg/react-template-pdf

Repository files navigation

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

About

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

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors