Skip to main content

Guide

react-pdf vs @react-pdf/renderer vs jsPDF 2026

Compare react-pdf, @react-pdf/renderer, and jsPDF for working with PDFs in React. PDF viewing, generation, rendering, and which PDF library to use in React.

·PkgPulse Team·
0

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

PDF Accessibility and Metadata

Accessibility in generated PDFs is a requirement for enterprise applications subject to WCAG or Section 508 compliance, and each library handles PDF accessibility differently. A PDF with accessibility support (PDF/UA) includes tagged content — logical reading order, alternative text for images, heading structure, and language declarations — that screen readers use to navigate the document.

jsPDF supports basic PDF metadata (title, author, subject, keywords) via doc.setProperties(), but has limited support for tagged PDF structure. The jspdf-autotable plugin generates table markup without semantic table tags, meaning screen readers encounter the data without column header associations. For documents that must pass automated accessibility checks, jsPDF's output will typically fail PDF/UA validation for complex content.

@react-pdf/renderer's approach to accessibility is through the aria namespace in its component props — <Text aria-label="Total amount">$99.00</Text> — but tagged PDF support is partial and evolving. The declarative component model makes it easier to produce consistently structured PDFs (headings are always headings, tables are always tables) compared to jsPDF's imperative approach, but the underlying PDF/UA tag generation is not yet complete. For government, healthcare, or financial services applications with strict accessibility requirements, neither jsPDF nor @react-pdf/renderer currently provides a complete PDF/UA solution without post-processing the output.

Document metadata — title, author, creation date, and language — should always be set in generated PDFs for both accessibility and document management reasons. @react-pdf/renderer accepts these as props on the <Document> component: <Document title="Invoice #001" author="PkgPulse Inc." language="en">. jsPDF accepts them via doc.setProperties() before calling doc.save(). Setting at minimum the title and language improves screen reader behavior and enables PDF management systems to index and search documents correctly.

Migration Guide

From jsPDF to @react-pdf/renderer

The paradigm shift is from imperative coordinate-based drawing to declarative JSX layout:

// jsPDF (old — coordinate-based)
import jsPDF from "jspdf"
const doc = new jsPDF()
doc.setFontSize(16)
doc.text("Invoice #001", 20, 30)
doc.setFontSize(12)
doc.text("Total: $99.00", 20, 50)
doc.save("invoice.pdf")

// @react-pdf/renderer (new — declarative JSX)
import { Document, Page, Text, View, StyleSheet, renderToBuffer } from "@react-pdf/renderer"
const styles = StyleSheet.create({ title: { fontSize: 16 }, body: { fontSize: 12 } })
function Invoice() {
  return (
    <Document>
      <Page style={{ padding: 40 }}>
        <Text style={styles.title}>Invoice #001</Text>
        <Text style={styles.body}>Total: $99.00</Text>
      </Page>
    </Document>
  )
}
// Server-side: const buffer = await renderToBuffer(<Invoice />)

The declarative approach scales dramatically better for complex documents. Once you need responsive layouts or conditional rendering based on data, @react-pdf/renderer's component model handles this naturally.

Community Adoption in 2026

jsPDF leads with approximately 2 million weekly downloads, reflecting its status as the oldest and most framework-agnostic JavaScript PDF library. It works in any frontend framework or vanilla JavaScript and has a large ecosystem of plugins, most notably jspdf-autotable. Its HTML-to-PDF approach via html2canvas is the fastest way to export an existing DOM element to PDF.

react-pdf (the viewer) reaches around 1 million weekly downloads, driven by applications that need to display existing PDF documents within a React interface. PDF viewers in legal document management, e-signature platforms, and document review tools are the primary use cases. It uses Mozilla's PDF.js under the hood.

@react-pdf/renderer sits at around 500,000 weekly downloads, growing steadily as more teams move PDF generation server-side to Next.js API routes. Its React component model makes it uniquely suited to teams that want to reuse their design system's typography and color tokens in generated documents — invoice components can share the same brand tokens as web components.

Font Handling and Internationalization

PDF font rendering is the most common source of unexpected output when working with any PDF generation library. Understanding how each library handles fonts prevents the most common production issues.

jsPDF's font embedding requires fonts to be base64-encoded and registered before use. The default Arial, Helvetica, and Courier fonts are built in, but custom fonts — or any font for non-Latin scripts — must be converted and registered manually. For Chinese, Japanese, Korean, or Arabic content, the correct font must be embedded or characters appear as boxes. jsPDF-AutoTable, the widely-used table plugin, inherits the same font system. Font files add significant PDF file size; embedding a Unicode CJK font can add 3-5MB to every generated PDF.

@react-pdf/renderer uses PDF's native font embedding through its Font.register() API. You register a font at a URL (remote or local), and the library fetches and embeds it when generating the PDF. The font subsetting feature is critical for file size — @react-pdf/renderer only embeds the Unicode code points actually used in the document, so a Chinese-heavy document embeds the full CJK subset but an English-only document embeds only the Latin glyphs. Google Fonts integration works directly: Font.register({ family: 'Inter', src: 'https://fonts.googleapis.com/css2?family=Inter...' }) resolves the CSS @font-face and fetches the appropriate WOFF2 file.

Right-to-left text support (Arabic, Hebrew) differs between libraries. jsPDF has an RTL plugin (jspdf-rtl) that reverses text direction at the document level. @react-pdf/renderer has no built-in RTL support and produces left-to-right text even for RTL scripts — a significant limitation for international applications. Neither library provides automatic bidirectional text handling (mixing LTR and RTL in the same line), which is the standard for documents with both English and Arabic content.

For server-side rendering in Next.js, consider caching generated PDFs by content hash. PDF generation with complex layouts and custom fonts can take 500ms-2s per document. A Redis or CDN cache keyed by the document's data fingerprint avoids regenerating identical documents on every request, which is especially important for invoice-heavy applications where the same document may be downloaded dozens of times.

For teams generating PDFs in Next.js Server Actions or API Routes, @react-pdf/renderer's renderToBuffer() function returns a Node.js Buffer that can be returned directly as a PDF response. Setting Content-Type: application/pdf and Content-Disposition: attachment; filename="document.pdf" in the response headers triggers the browser's download dialog or inline PDF viewer depending on user browser settings.

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 →

See also: React vs Vue and React vs Svelte, culori vs chroma-js vs tinycolor2.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.