Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To generate a dynamic PDF in React, build a document tree with @react-pdf/renderer primitives such as Document, Page, View, and Text. Put changing values in component props, then choose whether the PDF is rendered in the browser for preview/download or on a server for files and streams. The library uses its own layout and styling system; it does not convert an arbitrary HTML subtree with ordinary browser CSS.
Install the PDF renderer
Install the package in the React application:
npm install @react-pdf/renderer --save
The PDF component is a React element, but its children must be PDF-specific primitives. A typical document has one Document root and one or more Page children. View provides layout, while Text displays text. Styling uses the library’s supported CSS properties and Flexbox-like layout model rather than the full browser CSS implementation.
Build a document from changing data
Keep the document definition deterministic and pass the record, rows, or user-entered values as ordinary props. Rendering a new tree with new props produces a PDF containing the current data.
import React from 'react';
import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer';
const styles = StyleSheet.create({
page: { padding: 36, fontSize: 11, fontFamily: 'Helvetica' },
title: { fontSize: 20, marginBottom: 16 },
row: { flexDirection: 'row', borderBottomWidth: 1, paddingVertical: 6 },
name: { flex: 1 },
amount: { width: 90, textAlign: 'right' },
total: { marginTop: 14, fontSize: 14, textAlign: 'right' }
});
export function InvoiceDocument({ invoice }) {
const total = invoice.items.reduce((sum, item) => sum + item.quantity * item.price, 0);
return (
<Document title={`Invoice ${invoice.number}`} author="Example App">
<Page size="A4" style={styles.page}>
<Text style={styles.title}>Invoice {invoice.number}</Text>
<Text>Customer: {invoice.customerName}</Text>
<View style={{ marginTop: 18 }}>
{invoice.items.map((item) => (
<View style={styles.row} key={item.id}>
<Text style={styles.name}>{item.description} × {item.quantity}</Text>
<Text style={styles.amount}>${(item.quantity * item.price).toFixed(2)}</Text>
</View>
))}
</View>
<Text style={styles.total}>Total: ${total.toFixed(2)}</Text>
</Page>
</Document>
);
}
Do not pass a normal DOM element such as a browser div tree and expect it to be captured automatically. Convert the data into the renderer’s primitives and use only styles supported by its styling API.
#1 Best Overall
Render in the browser
Show an embedded preview
The quick-start workflow includes PDFViewer for displaying the generated document in the browser:
import { PDFViewer } from '@react-pdf/renderer';
import { InvoiceDocument } from './InvoiceDocument';
export function InvoicePreview({ invoice }) {
return (
<PDFViewer style={{ width: '100%', height: '800px' }}>
<InvoiceDocument invoice={invoice} />
</PDFViewer>
);
}
This is useful when users need to inspect the result before saving it. Treat the viewer as a browser-rendering feature; it is separate from your application’s normal HTML layout.
Offer a download link
For a direct download action, use PDFDownloadLink. Its child function receives loading state, so the button can reflect document generation:
import { PDFDownloadLink } from '@react-pdf/renderer';
import { InvoiceDocument } from './InvoiceDocument';
export function InvoiceDownload({ invoice }) {
return (
<PDFDownloadLink
document={<InvoiceDocument invoice={invoice} />}
fileName={`invoice-${invoice.number}.pdf`}
>
{({ loading, error }) => {
if (error) return 'Could not create PDF';
return loading ? 'Preparing PDF…' : 'Download invoice';
}}
</PDFDownloadLink>
);
}
Get the bytes yourself
Use BlobProvider when you need a blob URL or blob state, or call the documented pdf(...).toBlob() flow for custom handling such as uploading the result. The usePDF hook is appropriate when you want explicit control over recomputation:
import { usePDF } from '@react-pdf/renderer';
import { InvoiceDocument } from './InvoiceDocument';
export function ControlledDownload({ invoice }) {
const [instance, update] = usePDF({
document: <InvoiceDocument invoice={invoice} />
});
return (
<div>
<button onClick={() => update(<InvoiceDocument invoice={invoice} />)}>
Refresh PDF
</button>
{instance.loading && <p>Rendering…</p>}
{instance.error && <p>{String(instance.error)}</p>}
{instance.url && <a href={instance.url} download="invoice.pdf">Download</a>}
</div>
);
}
PDF recomputation can be expensive for large documents. Avoid rebuilding the document for unrelated UI state changes; update it when the relevant data changes or when the user requests a refresh.
Render on the server
Server rendering is useful when the PDF must be generated from protected data, scheduled in a job, stored, or returned by an API. The package documents file and stream output. A Node-style file example is:
import React from 'react';
import { renderToFile } from '@react-pdf/renderer';
import { InvoiceDocument } from './InvoiceDocument.js';
await renderToFile(
<InvoiceDocument invoice={invoice} />,
`./tmp/invoice-${invoice.number}.pdf`
);
For an HTTP endpoint, render a stream and set the response type to PDF:
Free tools Windows power users keep installed
One-click scans. No signup required.
import express from 'express';
import React from 'react';
import { renderToStream } from '@react-pdf/renderer';
import { InvoiceDocument } from './InvoiceDocument.js';
const app = express();
app.get('/invoices/:number.pdf', async (req, res) => {
const invoice = await loadInvoice(req.params.number);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader(
'Content-Disposition',
`attachment; filename="invoice-${invoice.number}.pdf"`
);
const stream = await renderToStream(<InvoiceDocument invoice={invoice} />);
stream.pipe(res);
});
Choose the location based on your data and delivery needs:
| Requirement | Browser rendering | Server rendering |
|---|---|---|
| Live preview | Strong fit with PDFViewer |
Requires a separate preview request |
| Download after a user edits data | Simple and immediate | Useful when data must remain on the server |
| Scheduled or bulk generation | Depends on a user’s browser | Better fit for jobs, storage, and queues |
| Secrets and private records | Data reaches the client | Keep data and generation on trusted infrastructure |
| Operational workload | Consumes the user’s device | Consumes server CPU and memory; monitor concurrency |
There is no universal winner. A common design is browser preview plus server-generated final copies when archival, access control, or repeatability matters.
Control pagination and changing page content
Let content wrap naturally
The renderer has an automatic wrapping engine. Text and breakable views can flow onto subsequent pages. Long lists should therefore be represented as repeated View and Text elements rather than one enormous string or a browser table.
Rank #3
Force a page break
Use break on an element when a new section must start on a new page:
Recommended Free Tools
<View break>
<Text>Terms and conditions</Text>
</View>
Keep an element together
Use wrap={false} for a block that must not split, such as a signature panel or a compact summary. Images are non-breaking by default according to the advanced guide; test large images because an item that cannot fit may move to the next page.
Repeat headers and footers
Mark a header or footer fixed so it is rendered on every page:
<Page size="A4" style={styles.page} wrap>
<View fixed style={styles.header}>
<Text>Acme Invoices</Text>
</View>
{/* flowing content */}
<View fixed style={styles.footer}>
<Text render={({ pageNumber, totalPages }) => `Page ${pageNumber} of ${totalPages}`} />
</View>
</Page>
A dynamic render callback can receive the page number and total page count. A Text render callback may be called twice while layout is resolved, so keep it deterministic and free of side effects. Do not increment counters, issue network requests, or mutate application state from it.
Fonts, metadata, and PDF/A
Document is the root and accepts Page children. It also supports document metadata, PDF version settings, and a conformance option for PDF/A output. The documented PDF/A support supplies XMP conformance metadata and an sRGB OutputIntent, but only b-level visual conformance is supported.
Rank #4
PDF/A requires embedded fonts. Register custom fonts instead of relying on the built-in standard 14 fonts when archival validation matters. Also verify that every glyph used by your data exists in the selected font; names, currency symbols, and non-Latin text are common sources of missing-glyph failures.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Common failures and fixes
The output is blank or missing fields
- Confirm that the value is available when the document component renders; server rendering cannot read client-only state.
- Check that the value is rendered inside
Text, not as an unrendered object or arbitrary DOM node. - Log the input data immediately before creating the
Document.
Styles look different from the web page
Use the renderer’s supported style properties and Flexbox layout. Browser-only CSS, selectors, inherited styles, and DOM measurements do not automatically apply. Recreate the layout with StyleSheet.create, explicit dimensions, and PDF primitives.
Rows split in an unwanted place
Put the row in a suitable View, use wrap={false} when the whole block must stay together, or insert break before a major section. Very large non-wrapping blocks can leave excessive white space.
The download link keeps regenerating
Stabilize the data and document inputs, and use usePDF when recomputation should occur only after an explicit update. Avoid creating new, irrelevant object values on every parent render.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Server output fails in deployment
Check that the chosen runtime supports the package’s server APIs, that fonts and image assets are available to the process, and that the process has permission to write temporary files. Prefer streaming for HTTP responses and handle rejected rendering promises before sending a success status.
Best Value
PDF/A validation fails
Register and embed fonts, use supported conformance settings, and validate the generated file with the validator required by your compliance process. The documented implementation supports only b-level visual conformance, so do not promise stronger PDF/A conformance without independent validation.
Or skip the browser setup
If your real task is capturing a changing web page rather than constructing a structured PDF from application data, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result.
One request is enough:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for the other 63 options, including full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, retina scale, PDF paper and margin controls, custom JavaScript and CSS, clicks, waits, request blocking, headers and cookies, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous webhooks, bulk capture, usage reporting, and the OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePython:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is included on every plan. Create a free ScreenshotNeo account.
FAQ
Can I convert an existing React component directly to PDF?
Not as ordinary browser markup. Recreate the output with Document, Page, View, Text, and the renderer’s style system.
Should generation happen in the browser or on the server?
Use the browser for interactive previews and user-triggered downloads; use the server for protected data, scheduled work, storage, or API delivery.
Can a PDF include page numbers?
Yes. A fixed element with a dynamic render callback can display the current page and total page count.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

