Skip to main content

react-pdf vs @react-pdf/renderer vs jsPDF: PDF in React (2026)

·PkgPulse Team

TL;DR

react-pdf displays existing PDF files in React — a PDF viewer component powered by pdf.js, renders PDF pages to canvas, supports zoom/navigation/text selection. @react-pdf/renderer generates PDFs from React components — write PDF layouts with JSX, outputs PDF files, supports styling/images/fonts, used for invoices and reports. jsPDF generates PDFs in the browser — imperative API, text/images/tables, works in any JavaScript framework, not React-specific. In 2026: react-pdf for viewing PDFs, @react-pdf/renderer for generating PDFs with React components, jsPDF for simple PDF generation without React.

Key Takeaways

  • react-pdf: ~1M weekly downloads — PDF viewer, displays .pdf files, powered by pdf.js
  • @react-pdf/renderer: ~500K weekly downloads — PDF generator, React components → PDF
  • jsPDF: ~2M weekly downloads — PDF generator, imperative API, framework-agnostic
  • Different purposes: react-pdf views PDFs, the other two generate PDFs
  • @react-pdf/renderer uses JSX/CSS-like syntax — feels like writing React
  • jsPDF is simpler but less powerful for complex layouts

react-pdf

react-pdf — PDF viewer:

Basic viewer

import { Document, Page, pdfjs } from "react-pdf"
import "react-pdf/dist/esm/Page/TextLayer.css"
import "react-pdf/dist/esm/Page/AnnotationLayer.css"

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`

function PDFViewer({ url }: { url: string }) {
  const [numPages, setNumPages] = useState(0)
  const [pageNumber, setPageNumber] = useState(1)

  return (
    <div>
      <Document
        file={url}
        onLoadSuccess={({ numPages }) => setNumPages(numPages)}
      >
        <Page pageNumber={pageNumber} />
      </Document>

      <div>
        <button onClick={() => setPageNumber(Math.max(1, pageNumber - 1))}>
          Previous
        </button>
        <span>{pageNumber} / {numPages}</span>
        <button onClick={() => setPageNumber(Math.min(numPages, pageNumber + 1))}>
          Next
        </button>
      </div>
    </div>
  )
}

With zoom

function ZoomablePDF({ url }: { url: string }) {
  const [scale, setScale] = useState(1.0)
  const [numPages, setNumPages] = useState(0)

  return (
    <div>
      <div>
        <button onClick={() => setScale(s => Math.max(0.5, s - 0.25))}>−</button>
        <span>{Math.round(scale * 100)}%</span>
        <button onClick={() => setScale(s => Math.min(3, s + 0.25))}>+</button>
      </div>

      <Document file={url} onLoadSuccess={({ numPages }) => setNumPages(numPages)}>
        {Array.from({ length: numPages }, (_, i) => (
          <Page key={i + 1} pageNumber={i + 1} scale={scale} />
        ))}
      </Document>
    </div>
  )
}
function SearchablePDF({ url }: { url: string }) {
  return (
    <Document file={url}>
      <Page
        pageNumber={1}
        renderTextLayer={true}   // Enables text selection
        renderAnnotationLayer={true}  // Enables links
        customTextRenderer={({ str }) => {
          // Highlight search terms:
          if (searchTerm && str.includes(searchTerm)) {
            return str.replace(
              searchTerm,
              `<mark>${searchTerm}</mark>`
            )
          }
          return str
        }}
      />
    </Document>
  )
}

@react-pdf/renderer

@react-pdf/renderer — PDF generation with React:

Basic document

import {
  Document, Page, Text, View, StyleSheet, pdf,
} from "@react-pdf/renderer"

const styles = StyleSheet.create({
  page: { padding: 40, fontFamily: "Helvetica" },
  title: { fontSize: 24, marginBottom: 20, color: "#1a1a1a" },
  section: { marginBottom: 10 },
  text: { fontSize: 12, lineHeight: 1.6, color: "#374151" },
})

function Invoice() {
  return (
    <Document>
      <Page size="A4" style={styles.page}>
        <Text style={styles.title}>Invoice #001</Text>
        <View style={styles.section}>
          <Text style={styles.text}>PkgPulse Inc.</Text>
          <Text style={styles.text}>Date: March 9, 2026</Text>
        </View>
        <View style={styles.section}>
          <Text style={styles.text}>Item: API Access — $99.00</Text>
          <Text style={styles.text}>Total: $99.00</Text>
        </View>
      </Page>
    </Document>
  )
}

// Generate PDF blob:
const blob = await pdf(<Invoice />).toBlob()

// Or render in browser:
import { PDFViewer } from "@react-pdf/renderer"

function App() {
  return (
    <PDFViewer width="100%" height="600px">
      <Invoice />
    </PDFViewer>
  )
}

Tables and complex layouts

import { Document, Page, Text, View, StyleSheet } from "@react-pdf/renderer"

const styles = StyleSheet.create({
  table: { display: "flex", width: "100%", borderStyle: "solid", borderWidth: 1 },
  tableRow: { flexDirection: "row" },
  tableHeader: { backgroundColor: "#3b82f6", color: "white" },
  tableCell: { flex: 1, padding: 8, borderWidth: 1, borderColor: "#e5e7eb", fontSize: 10 },
})

function PackageReport({ packages }: { packages: Package[] }) {
  return (
    <Document>
      <Page size="A4" style={{ padding: 40 }}>
        <Text style={{ fontSize: 20, marginBottom: 20 }}>
          Package Report
        </Text>

        <View style={styles.table}>
          {/* Header */}
          <View style={[styles.tableRow, styles.tableHeader]}>
            <Text style={styles.tableCell}>Package</Text>
            <Text style={styles.tableCell}>Downloads</Text>
            <Text style={styles.tableCell}>Version</Text>
          </View>

          {/* Rows */}
          {packages.map((pkg) => (
            <View key={pkg.name} style={styles.tableRow}>
              <Text style={styles.tableCell}>{pkg.name}</Text>
              <Text style={styles.tableCell}>{pkg.downloads.toLocaleString()}</Text>
              <Text style={styles.tableCell}>{pkg.version}</Text>
            </View>
          ))}
        </View>
      </Page>
    </Document>
  )
}

Images and custom fonts

import { Document, Page, Text, Image, Font } from "@react-pdf/renderer"

// Register custom font:
Font.register({
  family: "Inter",
  fonts: [
    { src: "/fonts/Inter-Regular.ttf", fontWeight: 400 },
    { src: "/fonts/Inter-Bold.ttf", fontWeight: 700 },
  ],
})

function BrandedReport() {
  return (
    <Document>
      <Page size="A4" style={{ padding: 40, fontFamily: "Inter" }}>
        <Image src="/logo.png" style={{ width: 120, marginBottom: 20 }} />
        <Text style={{ fontSize: 24, fontWeight: 700 }}>
          PkgPulse Annual Report
        </Text>
        <Image
          src="https://api.pkgpulse.com/charts/downloads.png"
          style={{ width: "100%", marginTop: 20 }}
        />
      </Page>
    </Document>
  )
}

Server-side generation

import { renderToBuffer } from "@react-pdf/renderer"

// Generate PDF on server (Node.js):
async function generateInvoicePDF(data: InvoiceData) {
  const buffer = await renderToBuffer(<Invoice data={data} />)

  // Save to file:
  fs.writeFileSync("invoice.pdf", buffer)

  // Or return as response:
  return new Response(buffer, {
    headers: {
      "Content-Type": "application/pdf",
      "Content-Disposition": "attachment; filename=invoice.pdf",
    },
  })
}

jsPDF

jsPDF — imperative PDF generation:

Basic usage

import jsPDF from "jspdf"

const doc = new jsPDF()

// Add text:
doc.setFontSize(24)
doc.text("Invoice #001", 20, 30)

doc.setFontSize(12)
doc.text("PkgPulse Inc.", 20, 50)
doc.text("Date: March 9, 2026", 20, 60)

doc.text("Item: API Access — $99.00", 20, 80)
doc.text("Total: $99.00", 20, 90)

// Save:
doc.save("invoice.pdf")

// Or get as blob:
const blob = doc.output("blob")

Tables (with autoTable plugin)

import jsPDF from "jspdf"
import autoTable from "jspdf-autotable"

const doc = new jsPDF()

doc.setFontSize(20)
doc.text("Package Report", 14, 22)

autoTable(doc, {
  startY: 30,
  head: [["Package", "Downloads", "Version"]],
  body: [
    ["react", "25,000,000", "19.0.0"],
    ["vue", "5,000,000", "3.5.0"],
    ["svelte", "2,000,000", "5.0.0"],
  ],
  theme: "striped",
  headStyles: { fillColor: [59, 130, 246] },
})

doc.save("report.pdf")

Images

import jsPDF from "jspdf"

const doc = new jsPDF()

// Add image from URL/base64:
const imgData = "data:image/png;base64,..."
doc.addImage(imgData, "PNG", 15, 15, 50, 20)

// From canvas:
const canvas = document.getElementById("chart") as HTMLCanvasElement
doc.addImage(canvas, "PNG", 15, 50, 180, 100)

doc.save("report-with-images.pdf")

HTML to PDF

import jsPDF from "jspdf"
import html2canvas from "html2canvas"

// Convert HTML element to PDF:
const element = document.getElementById("report")!

const canvas = await html2canvas(element)
const imgData = canvas.toDataURL("image/png")

const doc = new jsPDF()
const imgWidth = 190
const imgHeight = (canvas.height * imgWidth) / canvas.width

doc.addImage(imgData, "PNG", 10, 10, imgWidth, imgHeight)
doc.save("from-html.pdf")

Feature Comparison

Featurereact-pdf@react-pdf/rendererjsPDF
PurposeView PDFsGenerate PDFsGenerate PDFs
API styleReact componentsReact components (JSX)Imperative
React-specific❌ (any framework)
InputPDF file/URLJSX componentsCode
OutputRendered canvasPDF file/blobPDF file/blob
Flexbox layoutN/A
TablesN/AManual (View/Text)✅ (autoTable)
Custom fontsN/A
ImagesN/A
Server-side✅ (renderToBuffer)✅ (Node.js)
HTML to PDFN/A✅ (html2canvas)
Text selectionN/AN/A
Page navigationN/AN/A
Weekly downloads~1M~500K~2M

When to Use Each

Use react-pdf if:

  • Need to display/view existing PDF files in React
  • Building a PDF viewer component
  • Need text selection, zoom, and page navigation
  • Working with user-uploaded PDF documents

Use @react-pdf/renderer if:

  • Need to generate PDFs using React components
  • Building invoices, reports, or certificates
  • Want JSX/CSS-like syntax for PDF layout
  • Need server-side PDF generation in Node.js

Use jsPDF if:

  • Need simple PDF generation in any framework
  • Want HTML-to-PDF conversion
  • Need table support (autoTable plugin)
  • Building a lightweight PDF export feature

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on react-pdf v9.x, @react-pdf/renderer v4.x, and jsPDF v2.x.

Compare PDF tooling and React libraries on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.