<!-- PkgPulse AI-readable guide source -->
<!-- Canonical: https://www.pkgpulse.com/guides/react-pdf-vs-react-pdf-renderer-vs-jspdf-pdf-in-react-2026 -->
<!-- Raw Markdown: https://www.pkgpulse.com/guides/react-pdf-vs-react-pdf-renderer-vs-jspdf-pdf-in-react-2026/raw.md -->
<!-- Source path: content/guides/react-pdf-vs-react-pdf-renderer-vs-jspdf-pdf-in-react-2026.mdx -->

---
og_image: "/images/guides/react-pdf-vs-react-pdf-renderer-vs-jspdf-pdf-in-react-2026.webp"
title: "react-pdf vs @react-pdf/renderer vs jsPDF 2026"
description: "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."
date: "2026-03-09"
author: "PkgPulse Team"
tags: ["react", "typescript", "developer-tools", "documents"]
---

## 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](https://github.com/wojtekmaj/react-pdf) — PDF viewer:

### Basic viewer

```tsx
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

```tsx
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>
  )
}
```

### Text selection and search

```tsx
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](https://react-pdf.org) — PDF generation with React:

### Basic document

```tsx
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

```tsx
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

```tsx
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

```typescript
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](https://github.com/parallax/jsPDF) — imperative PDF generation:

### Basic usage

```typescript
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)

```typescript
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

```typescript
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

```typescript
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

| Feature | react-pdf | @react-pdf/renderer | jsPDF |
|---------|----------|-------------------|-------|
| Purpose | View PDFs | Generate PDFs | Generate PDFs |
| API style | React components | React components (JSX) | Imperative |
| React-specific | ✅ | ✅ | ❌ (any framework) |
| Input | PDF file/URL | JSX components | Code |
| Output | Rendered canvas | PDF file/blob | PDF file/blob |
| Flexbox layout | N/A | ✅ | ❌ |
| Tables | N/A | Manual (View/Text) | ✅ (autoTable) |
| Custom fonts | N/A | ✅ | ✅ |
| Images | N/A | ✅ | ✅ |
| Server-side | ❌ | ✅ (renderToBuffer) | ✅ (Node.js) |
| HTML to PDF | N/A | ❌ | ✅ (html2canvas) |
| Text selection | ✅ | N/A | N/A |
| Page navigation | ✅ | N/A | N/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:

```typescript
// 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 →](https://www.pkgpulse.com)*

*See also: [React vs Vue](/compare/react-vs-vue) and [React vs Svelte](/compare/react-vs-svelte), [culori vs chroma-js vs tinycolor2](/guides/culori-vs-chroma-js-vs-tinycolor2-color-manipulation-2026).*
