Generating PDFs with React-PDF
When to Use
-
Generating PDF documents (reports, invoices, resumes, forms, certificates)
-
Creating PDFs that need complex layouts (multi-column, grids, cards)
-
Building PDFs with inline SVG graphics, charts, or icons
-
Producing documents with custom Google Fonts or emoji
-
Any PDF task where flexbox layout is easier than absolute coordinate math
When NOT to Use
-
Reading or extracting text from existing PDFs — use pdfplumber or pypdf instead
-
Filling existing PDF forms — use Python pypdf or pdftk
-
Converting HTML to PDF — use Playwright or WeasyPrint instead
-
Simple one-off text PDFs with no layout needs — a Python script may be simpler
CRITICAL REQUIREMENTS
-
Fonts MUST be local files - Remote font URLs (http/https) do NOT work. Always download fonts to local files before using them.
-
Wrap async code in IIFE - Top-level await causes errors. Always use (async () => { ... })()
pattern.
- Disable hyphenation for custom fonts - Custom fonts lack hyphenation dictionaries and may crash or break words incorrectly. Always call Font.registerHyphenationCallback((word) => [word]); after registering custom fonts.
Files
-
{baseDir}/skills/react-pdf/references/google-fonts.txt
-
Metadata for ~65 popular Google Fonts with TrueType URLs. Each line is a font variant in tab-separated format: font name , style , category , weight , url .
-
{baseDir}/skills/react-pdf/references/components.md
-
Full component API reference and supported CSS properties
-
{baseDir}/skills/react-pdf/assets/example-template.tsx
-
Minimal working example demonstrating fixed footers, page numbers, and unbreakable content. Read this before starting to understand the basic patterns. Note: not all APIs are shown here — always refer to the docs and {baseDir}/skills/react-pdf/references/components.md for the full API.
Prerequisites
npm install react @react-pdf/renderer npm install -D tsx @types/react
tsx runs TypeScript + JSX files directly via Node with no config — no tsconfig.json needed. It uses esbuild under the hood and handles JSX transformation automatically.
Core Components
-
Document: Root component (metadata, settings)
-
Page: Individual pages (A4, Letter, or custom dimensions)
-
View: Container component (similar to div)
-
Text: Text content, supports nesting for inline styling
-
Image: Embed images (JPG, PNG, base64)
-
Link: Clickable hyperlinks (external or internal)
-
Note: Annotation notes
-
Canvas: Freeform drawing with pdfkit methods
-
Svg: Vector graphics (Circle, Rect, Path, Line, Polygon, etc.)
-
StyleSheet: Create reusable styles
For full component props and CSS properties, see references/components.md.
Basic Example
import React from "react"; import { Document, Page, Text, View, StyleSheet, renderToFile } from "@react-pdf/renderer";
const styles = StyleSheet.create({ page: { flexDirection: "column", backgroundColor: "#ffffff", padding: 40 }, title: { fontSize: 24, marginBottom: 20, fontWeight: "bold" }, text: { fontSize: 12, lineHeight: 1.5 }, });
const MyDocument = () => ( <Document> <Page size="A4" style={styles.page}> <View style={{ margin: 10, padding: 20 }}> <Text style={styles.title}>Document Title</Text> <Text style={styles.text}>Your content here</Text> </View> </Page> </Document> );
(async () => { await renderToFile(<MyDocument />, "./output.pdf"); console.log("PDF saved!"); })();
Running Scripts
PDF generation scripts use JSX, which Node cannot run directly. Use tsx to execute them:
npx tsx my-document.tsx
npx tsx works without installing tsx globally — it downloads on demand. If tsx is installed as a dev dependency (npm install -D tsx ), it runs instantly without the npx download step.
Always wrap rendering in async IIFE:
// Good (async () => { await renderToFile(<MyDocument />, "./output.pdf"); })();
// Bad - top-level await may fail await renderToFile(<MyDocument />, "./output.pdf");
Previewing PDFs
To visually inspect generated PDFs, convert pages to images. Try pdftoppm first (often pre-installed), fall back to Python's PyMuPDF if unavailable.
Option 1: pdftoppm (poppler-utils) — preferred, no install needed in many environments:
pdftoppm -png -r 200 document.pdf preview
→ preview-1.png, preview-2.png, ...
Option 2: PyMuPDF (Python) — fallback if pdftoppm is not available:
pip install pymupdf
import fitz
doc = fitz.open("document.pdf") for i, page in enumerate(doc): pix = page.get_pixmap(dpi=200) pix.save(f"page-{i+1}.png")
Rendering Methods
import { renderToFile, renderToBuffer } from "@react-pdf/renderer";
// To file (async () => { await renderToFile(<MyDocument />, "./document.pdf"); })();
// To buffer (async () => { const buffer = await renderToBuffer(<MyDocument />); })();
Styling
Three methods: StyleSheet.create() , inline objects, or mixed arrays.
const styles = StyleSheet.create({ container: { padding: 20 } });
<View style={styles.container} /> <View style={{ padding: 20 }} /> <View style={[styles.container, { marginTop: 10 }]} />
Supported Units
pt (default, 72 DPI), in , mm , cm , % , vw , vh
Common Style Properties
{ // Flexbox flexDirection: "row", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 10,
// Box model margin: 10, padding: 20, width: "100%", height: 200,
// Borders borderWidth: 1, borderColor: "#333", borderRadius: 5, borderStyle: "solid",
// Colors backgroundColor: "#f0f0f0", color: "#000", opacity: 0.8,
// Typography fontSize: 12, fontWeight: "bold", fontFamily: "Helvetica", fontStyle: "italic", lineHeight: 1.5, textAlign: "center", textDecoration: "underline", textTransform: "uppercase", letterSpacing: 1,
// Position position: "absolute", top: 0, left: 0, right: 0, bottom: 0, zIndex: 10,
// Transforms transform: "rotate(45deg)", transformOrigin: "center", }
Images
Local files are most reliable. Remote URLs may fail due to network/CORS issues.
import { Image } from '@react-pdf/renderer';
<Image src="./images/photo.jpg" style={{ width: 200, height: 150 }} /> <Image src={{ data: buffer, format: 'png' }} />
SVG files cannot be used as Image sources. Read the SVG source and recreate using react-pdf Svg components.
SVG Graphics
import { Svg, Circle, Rect, Path, Line, G, Defs, LinearGradient, Stop } from "@react-pdf/renderer";
<Svg width="200" height="200" viewBox="0 0 200 200"> <Defs> <LinearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%"> <Stop offset="0%" stopColor="#3498db" /> <Stop offset="100%" stopColor="#9b59b6" /> </LinearGradient> </Defs> <Circle cx="100" cy="100" r="50" fill="url(#grad1)" /> <Rect x="10" y="10" width="50" height="50" fill="#e74c3c" /> <Path d="M10,50 Q50,10 90,50" stroke="#2ecc71" strokeWidth="2" fill="none" /> </Svg>;
Using Icons
Read SVG source from icon libraries and convert to react-pdf Svg components:
npm install lucide-static
import { Svg, Path, Rect } from "@react-pdf/renderer";
// Converted from lucide-static/icons/mail.svg const MailIcon = ({ size = 12, color = "#888" }) => ( <Svg width={size} height={size} viewBox="0 0 24 24"> <Path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" stroke={color} strokeWidth={2} fill="none" /> <Rect x="2" y="4" width="20" height="16" rx="2" stroke={color} strokeWidth={2} fill="none" /> </Svg> );
Links and Navigation
<Link src="https://example.com"><Text>Visit website</Text></Link>
<View id="section-1"><Text>Target</Text></View> <Link src="#section-1"><Text>Jump to Section 1</Text></Link>
Dynamic Content and Page Numbers
<Text render={({ pageNumber, totalPages }) => Page ${pageNumber} of ${totalPages}} />
Fixed Headers/Footers
<Page size="A4">
<View fixed style={{ position: "absolute", top: 20, left: 30, right: 30 }}>
<Text>Header</Text>
</View>
<View style={{ marginTop: 60, marginBottom: 60 }}>
<Text>Content</Text>
</View>
<Text
fixed
style={{ position: "absolute", bottom: 20, left: 30, right: 30, textAlign: "center" }}
render={({ pageNumber, totalPages }) => Page ${pageNumber} of ${totalPages}}
/>
</Page>
Page Breaks and Wrapping
<View break /> // Force page break <View wrap={false}><Text>Keep together</Text></View> // Prevent breaking inside <Text orphans={2} widows={2}>Long text...</Text> // Orphan/widow control <View minPresenceAhead={100}><Text>Content</Text></View> // Min space before break
Custom Fonts
CRITICAL: All font sources MUST be local file paths. Remote URLs do not work.
import { Font } from "@react-pdf/renderer";
Font.register({ family: "Roboto", fonts: [ { src: "./fonts/Roboto-Regular.ttf", fontWeight: "normal" }, { src: "./fonts/Roboto-Bold.ttf", fontWeight: "bold" }, { src: "./fonts/Roboto-Italic.ttf", fontStyle: "italic" }, ], });
// Always disable hyphenation when using custom fonts Font.registerHyphenationCallback((word) => [word]);
Built-in fonts: Courier, Helvetica, Times-Roman (each with Bold, Italic/Oblique variants)
Font weight values: thin (100), ultralight (200), light (300), normal (400), medium (500), semibold (600), bold (700), ultrabold (800), heavy (900)
Google Fonts
Use {baseDir}/skills/react-pdf/references/google-fonts.txt to find font URLs, then download locally:
Find the font URL
grep "^Roboto" {baseDir}/skills/react-pdf/references/google-fonts.txt | grep "700" | grep "normal"
Download
mkdir -p fonts curl -sL "<url-from-grep>" -o fonts/Roboto-Bold.ttf
Verify - must show "TrueType Font data"
file fonts/Roboto-Bold.ttf
If file shows "HTML document" or "ASCII text", the download failed. Try a different URL or search GitHub for the font's official repo with TTF files.
Emoji
Emoji won't render in PDFs unless you register an emoji source. Install twemoji-emojis to get local Twemoji PNG assets — no internet needed at render time.
npm install twemoji-emojis
import { Font } from "@react-pdf/renderer";
Font.registerEmojiSource({ format: "png", url: "node_modules/twemoji-emojis/vendor/72x72/", });
Then use emoji directly in Text: <Text>Hello</Text>
Other Features
// Canvas drawing <Canvas style={{ width: 200, height: 200 }} paint={(painter, w, h) => { painter.circle(w/2, h/2, 50).fill("#3498db"); }} />
// Annotation notes <Note style={{ color: "yellow" }}>Annotation text</Note>
// Hyphenation Font.registerHyphenationCallback((word) => [word]); // disable
// Debug mode - visualize boundaries <View debug><Text debug>Debug text</Text></View>
// Document metadata <Document title="My Doc" author="Author" subject="Report" language="en-US" pdfVersion="1.5" />
Best Practices
-
Use StyleSheet.create() — define styles once and reuse
-
Compress images before embedding, use cache={true} for remote images
-
Test page breaks — content may flow differently than expected
-
Prefer flexbox over absolute positioning
-
Use fixed prop for headers/footers on every page
-
Use debug={true} to visualize element boundaries
-
Wrap rendering in try-catch blocks
Common Issues
Text overflow: <Text style={{ width: 200, maxLines: 3, textOverflow: "ellipsis" }}>...</Text>
Missing fonts: Download locally and register with local file paths. Remote URLs will NOT work.
Unexpected page breaks: Use wrap={false} to keep content together, or <View break /> to force breaks.