react-pdf vs @react-pdf/renderer vs jsPDF: PDF in React (2026)
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>
)
}
Text selection and search
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
| 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
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.