Use html2pdf.js when the PDF should resemble a rendered React div, and use jsPDF-AutoTable when table rows, repeated headings, or wide columns need precise pagination. Both run in the browser, but they solve different problems: DOM capture preserves much of a view’s appearance, while a data-driven table gives you explicit control over rows and page breaks.
This guide shows a complete React implementation, page-break CSS, table strategies, validation checks, and an API alternative when browser-side generation is not the right deployment model.
Choose the PDF strategy before writing code
| Requirement | Best fit | Why |
|---|---|---|
| Preserve the appearance of a rendered card, report, or dashboard | html2pdf.js |
Captures a browser-rendered element through html2canvas and writes it with jsPDF. |
| Repeat table headings and control row pagination | jsPDF-AutoTable |
Builds rows directly from data or an HTML table and exposes page-break options. |
| Generate on a server or from a URL | Hosted HTML-to-PDF API | Keeps generation and credentials away from the browser; API behavior depends on the provider. |
These approaches are not interchangeable. A responsive table that looks good on a monitor may become unreadable when squeezed onto a portrait page. Conversely, rebuilding a complex dashboard as PDF rows can discard useful visual context.
Convert a React div with html2pdf.js
Install the packages
npm install html2pdf.js
The library is browser-only and does not run in Node.js. Import it in the component or in code that executes after the client has rendered the target element. Its documented pipeline uses html2canvas and jsPDF; the result is a DOM-derived rendering, not the browser’s print engine.
#1 Best Overall
Create a stable export target
import { useRef, useState } from 'react';
import html2pdf from 'html2pdf.js';
export default function Report() {
const reportRef = useRef(null);
const [exporting, setExporting] = useState(false);
async function downloadPdf() {
if (!reportRef.current) return;
setExporting(true);
try {
const options = {
margin: [12, 12, 12, 12],
filename: 'react-report.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff'
},
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
pagebreak: { mode: ['css', 'legacy'] }
};
await html2pdf().set(options).from(reportRef.current).save();
} finally {
setExporting(false);
}
}
return (
<>
Quarterly report
This content is rendered by React before capture.
Details
Use a semantic section when a new page is required.
>
);
}
The ref must point to an element that exists after React has rendered its final content. If data arrives asynchronously, disable the export button until loading, images, fonts, and charts have settled. Generate from a dedicated export wrapper when your on-screen layout uses responsive navigation or controls that should not appear in the document.
Control page breaks with CSS
.pdf-report {
background: #fff;
color: #111;
width: 100%;
}
.pdf-section {
break-inside: avoid;
page-break-inside: avoid;
margin-bottom: 18px;
}
.pdf-page-break-before {
break-before: page;
page-break-before: always;
}
.pdf-page-break-after {
break-after: page;
page-break-after: always;
}
@media print {
.screen-only { display: none; }
}
html2pdf.js supports CSS/page-break rules and its legacy break behavior. Use an explicit break for a chapter, invoice, or other semantic boundary; do not expect an arbitrary long element to split at an attractive location. Keep the break class on a block-level element that is present in the captured DOM.
Useful capture options
margin,format, andorientation: establish the PDF page geometry. Match the document to A4, Letter, portrait, or landscape rather than relying on defaults.scale: a higher canvas scale can improve text and line sharpness but increases memory use and output size.image.typeandquality: JPEG is smaller for photographic content; PNG is preferable for crisp transparency or flat graphics.useCORS: allows images that provide the right cross-origin headers to be requested for canvas rendering. It cannot bypass browser security policy.pagebreak.mode: combine CSS rules with the library’s legacy behavior when migrating an existing layout.
Why a rendered div is not a literal screenshot
html2canvas documentation explains that it builds a representation from DOM properties rather than taking a pixel-perfect screenshot of the page. Only supported CSS properties are reproduced. Test the exact typography, gradients, filters, sticky elements, SVGs, charts, and overflow behavior used by your app.
Cross-origin images and iframes
Cross-origin iframes cannot be rendered because the browser prevents access to their document. Cross-origin images without appropriate CORS headers can taint the canvas, making it unreadable. Host assets on the same origin where practical, configure image responses with the required headers, or replace external embeds with export-safe content.
Make the export deterministic
- Wait until API data, fonts, and images have loaded.
- Use fixed colors and widths in export CSS instead of relying only on viewport breakpoints.
- Hide buttons, tooltips, sticky headers, chat controls, and animation during export.
- Give long text a defined wrapping policy and avoid unbreakable strings such as huge URLs.
- Render charts to export-safe SVG or canvas and verify them in every supported browser.
Build long or wide tables with jsPDF-AutoTable
Install and import
npm install jspdf jspdf-autotable
import { jsPDF } from 'jspdf';
import autoTable from 'jspdf-autotable';
export function downloadOrdersPdf(orders) {
const doc = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
doc.setFontSize(16);
doc.text('Orders', 14, 16);
autoTable(doc, {
startY: 24,
head: [['Order', 'Customer', 'Date', 'Status', 'Total']],
body: orders.map(order => [
order.number,
order.customer,
order.date,
order.status,
order.total
]),
theme: 'grid',
styles: { fontSize: 8, cellPadding: 2 },
headStyles: { fillColor: [31, 78, 121], textColor: 255 },
margin: { left: 12, right: 12 },
rowPageBreak: 'avoid',
pageBreak: 'auto',
showHead: 'everyPage'
});
doc.save('orders.pdf');
}
The documented AutoTable options include row and table page behavior, repeated headings, and horizontal page breaks for wide tables. rowPageBreak: 'avoid' keeps a row together when possible; a row taller than a page still has to split. Inspect the output with your largest real row, not only short sample data.
Use an existing HTML table when appropriate
const table = document.querySelector('#orders-table');
if (!table) throw new Error('Orders table was not rendered');
autoTable(doc, {
html: table,
startY: 20,
head: [['Order', 'Customer', 'Date', 'Status', 'Total']],
showHead: 'everyPage',
rowPageBreak: 'avoid'
});
AutoTable accepts a CSS selector or an HTMLTableElement. Parsing an existing table is convenient, but a data-driven call is usually more predictable when the screen table is responsive, virtualized, sorted differently, or contains controls and hidden columns.
Rank #3
Handle wide tables
Prefer landscape orientation, shorter headings, explicit column widths, and sensible font sizes. If columns still exceed the page, use AutoTable’s horizontal page-break support and test how readers will join the column panels. Do not solve width by shrinking text until it becomes illegible. For very wide analytical data, consider separate logical tables or a detail page per record.
Combine a visual report and a controlled table
A common design is to capture the cover, charts, and narrative with html2pdf.js, then generate a separate table with AutoTable. Merging independently generated PDFs requires an additional PDF-merging step; otherwise, offer two downloads or place the table in the same DOM export. Decide this architecture before styling so that fonts, page numbers, and margins remain consistent.
Hosted generation and credential safety
A hosted HTML-to-PDF API is useful when PDFs must be generated from a server, a URL, a queue, or a scheduled job. Keep API keys on a trusted server; do not put them in React bundles, browser network calls, or public repositories. The API documentation at HTML2PDF.app documentation states this credential-safety principle, but provider features, rendering engines, limits, and pricing must be checked for the service you choose.
Rank #4
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One request can return a PNG, JPEG, WebP, or PDF, making it useful when your React app’s output is already available at a URL and you do not want to maintain browser capture code.
Install no browser automation for this call. Keep the access key on your server and request the URL:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →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 complete option list and response details in the ScreenshotNeo documentation. ScreenshotNeo can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be switched off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether the request was billed. It also includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. For a React page that needs a clean URL-based PDF rather than a DOM ref, create a free ScreenshotNeo account.
Best Value
Performance, reliability, and cost checks
Browser memory and responsiveness
Large DOM trees, high scale values, and huge images can exhaust canvas memory or freeze the tab. Export a bounded report node, downsize source images, paginate data before rendering, and show a busy state. Avoid exporting thousands of virtualized rows that are not actually in the DOM.
Reliable page boundaries
Use explicit semantic breaks for sections and automated visual checks for the first, middle, and last pages. Include long paragraphs, multiline cells, missing images, and the longest labels in test fixtures. A successful download only proves that a PDF file was written; it does not prove that content is visible or correctly paginated.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →File size and quality
Higher canvas scale and PNG output improve detail but increase memory and file size. JPEG quality reduces size for photographs but can blur small text. Choose settings per document type, then inspect the resulting file on a normal laptop and phone.
Troubleshooting common failures
The PDF is blank
- Cause: the ref is null, export ran before data rendered, or a canvas/iframe failed.
- Fix: disable the button until content exists; log
reportRef.current; remove unsupported embeds; verify image CORS headers; retry with a minimal element.
Images or logos are missing
- Cause: the image was not loaded or is cross-origin without CORS permission.
- Fix: wait for image completion, serve it from your origin or with appropriate CORS headers, and enable
useCORSwhere permitted.
A section overlaps or splits badly
- Cause: the DOM layout has no break rule or contains fixed/sticky positioning.
- Fix: add
break-inside: avoidto manageable blocks andbreak-before: pageto semantic starts; replace fixed positioning in export CSS.
Table headings do not repeat
- Cause: the table was captured as a visual DOM or AutoTable’s heading option was omitted.
- Fix: generate the table with AutoTable and set
showHead: 'everyPage', or redesign the DOM table with repeated header sections.
Rows are unreadable on portrait pages
- Cause: too many columns are being forced into one page.
- Fix: switch to landscape, set column widths, shorten labels, split the table, or use horizontal page breaks.
Export fails during server-side rendering
- Cause:
html2pdf.jsand its canvas pipeline require browser APIs. - Fix: invoke export only after client hydration, or move generation to a server-capable PDF service.
Validation checklist before release
- Test the smallest and largest supported datasets.
- Check fonts, images, SVGs, charts, links, and RTL or international text if applicable.
- Open the PDF in at least two viewers and print one sample.
- Verify page count, repeated headings, margins, and intentional page starts.
- Confirm that private data is not accidentally exposed through a public URL or client-side API key.
- Measure browser time, memory use, and resulting file size on a representative device.
Frequently Asked Questions
Can html2pdf.js generate a PDF in a Node.js API route?
No. Its documented workflow depends on browser DOM and canvas APIs. Use a browser-side export after hydration or a server-capable PDF service instead.
Should I export a responsive HTML table or rebuild it with AutoTable?
Export the DOM when preserving the screen’s visual arrangement matters. Rebuild from data with AutoTable when row pagination, repeated headings, column widths, or wide-table handling matter more.
Why does a cross-origin iframe disappear from the PDF?
Browser same-origin security prevents html2canvas from reading the iframe document. Replace it with same-origin or export-safe content, or generate the document in an environment that can access the source.
Recommended Free Tools
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.

