Generating PDFs in the React ecosystem has always been awkward: jsPDF requires manually computed coordinates and heavy code with poor style fidelity; html2canvas produces bitmaps where text cannot be selected and large images come out blurry; Puppeteer demands a separate browser service to maintain. dompdf.js renders the DOM directly into vector PDFs — selectable text, correct Chinese characters, full table and pagination support — making it one of the most cost-effective options for React projects. This guide walks from installation and function-component integration through a custom usePdfExport hook to styling and pagination tips, so you can ship a reliable export feature in the shortest possible time. React's declarative rendering model is a natural match: whatever the component tree renders is precisely what the PDF captures, so there is no separate 'document model' to keep in sync. The library's small footprint also fits React projects that already ship a large bundle: one import, no peer dependencies, and no build step changes. This guide stays practical throughout — each section pairs a concrete pattern with the pitfalls that typically trip teams up, so the export feature ships clean the first time.
Reporting platforms, analytics tools, and e-commerce backends all need PDF export. React's component-based page structure is a natural fit for the pattern of "render a component region as a PDF," with the exported content staying consistent with the current page state. Export quality is often a purchasing criterion in enterprise deals, so investing in real vector PDFs rather than screenshots pays off in evaluation scores and renewal conversations.
Comparing common options: jsPDF's coordinate drawing has high development cost and is painful to maintain; html2canvas outputs bitmaps with non-selectable text and blurry large images; dompdf.js outputs vector PDFs with crisp, searchable text — a better balance of quality and effort. The comparison also matters for maintenance: coordinate-based code drifts as designs change, whereas DOM-based templates simply follow the component markup.
dompdf.js depends on neither a backend nor a browser plugin. A simple npm install makes it usable, and it works well with React's virtual DOM: it exports the real DOM after rendering, so content and page state are naturally in sync with no extra plumbing. Once a team has chosen a DOM-rendering approach, the same code serves every export feature in the product, which keeps the learning curve a one-time cost.
Analytics and BI dashboards are the most common React export consumers: users want the chart, the table, and the filter context in one document, and a snapshot of the rendered dashboard provides exactly that.
E-commerce backends export invoices, packing slips, and refund confirmations — again table-heavy, again a strong fit for DOM rendering with automatic pagination.
In React Native or React-based micro-frontend architectures, the export module can be shared as a package, so one implementation serves every team instead of each app reinventing it.
Teams migrating from older jQuery-era export plugins often find the React integration simpler than the legacy one, because the hook pattern matches React's own idioms — state, refs, and effects — instead of imperative global calls.
Install the dependency with npm install dompdf.js, then import { DomPDF } from 'dompdf.js' in a component and start using it. No extra configuration is required and the existing build pipeline is unaffected. Because the API surface is three methods, onboarding a new developer takes minutes: read the README example, adapt the source argument, and the feature works.
The core usage has three steps: create a DomPDF instance, call addPage with a DOM node or an HTML string, and call save to download the file. Multi-page documents are built with repeated addPage calls and exported in one save. Installation is also side-effect free: the package does not patch globals or register service workers, so it cannot break existing app behavior.
Both class and function components work. Function components with useRef to manage the export region are recommended: the code is cleaner, and the export logic is easier to wrap into a custom hook shared across pages. The same instance pattern works for both single-page exports and multi-page documents, so the mental model stays uniform across features.
A common first mistake is creating a new DomPDF instance per page inside a loop; prefer one instance for the whole document, adding pages in sequence, so the output is a single coherent file with continuous pagination.
For teams using module federation or CDN-loaded builds, the global script build exposes the same API, which keeps integration identical across delivery modes.
Keeping the export logic in a dedicated module also isolates the WASM loading cost: the import can be made dynamic so the PDF engine only loads when the user actually clicks export.
useRef binds the export region, and the click handler passes ref.current to addPage. By the time React has finished rendering, the DOM is the final layout, so the exported result matches the screen exactly with no extra synchronization logic. useRef is the right tool because it gives a stable reference across renders; the DOM node is guaranteed to reflect the latest state when the click handler runs.
When the content comes from an API, trigger export only after the data has loaded — use state to disable the button meanwhile so users cannot export an empty document before data arrives. For content that depends on asynchronous data, combine the ref with a ready flag: disable the export button until the request resolves, and the PDF can never capture an empty table.
Alternatively, assemble the API response into an HTML string and export that, which suits scenarios where the backend returns structured data and the frontend generates the PDF directly. Choose between DOM export and string export per use case. The same ref pattern extends naturally to several export regions on one page, since each region gets its own ref and its own addPage call.
When the report region contains interactive elements that should not appear in the PDF — buttons, filters, toggles — clone the region into a print-oriented container first, or hide the controls with a print-only class before export.
If the export must run right after a state update, remember that React batches updates; call the export in an effect keyed on the data, or pass the data directly to the template string, rather than reading the DOM too early.
Large reports can freeze the tab while the snapshot is taken; running the export in a deferred task keeps the click interaction snappy and lets a loading indicator paint first.
For reports assembled from several components, a single ref on a wrapper div captures them all; there is no need to stitch fragments together, because the DOM already represents the composed layout.
import { useRef } from 'react';
import { DomPDF } from 'dompdf.js';
export default function ReportPage() {
const reportRef = useRef(null);
const handleExport = () => {
const pdf = new DomPDF();
pdf.addPage(reportRef.current, { format: 'A4', margin: '15mm' });
pdf.save('report.pdf');
};
return (
<div>
<button onClick={handleExport}>Export PDF</button>
<div ref={reportRef}>
<h1>Monthly Business Report</h1>
<table>
<tbody>
<tr><td>Revenue</td><td>1.2M</td><td>+8%</td></tr>
</tbody>
</table>
</div>
</div>
);
}
Extracting export logic into a custom hook lets components care only about what to export. Paper size, margins, and filename are managed centrally, so multiple pages reuse the same code with zero duplication and a change in one place takes effect everywhere. Hooks also compose well with existing state management: the hook can read a global export configuration (default margins, branding) and merge it with per-call options.
The hook can also encapsulate error handling (try/catch with user notifications), exporting state, and progress callbacks, giving callers a more complete interaction without reimplementing it in every component. Returning a stable callback via useCallback keeps memoized children from re-rendering when they receive the export handler as a prop.
Used with TypeScript, the hook can define explicit types for source and options, making the interface clear for team collaboration, preventing argument mistakes, and surfacing type issues immediately during refactors. Because the hook centralizes the library calls, upgrading the engine later touches one file instead of every export button in the app.
The hook can expose more than one action: exportPdf for the current region, exportAll for a batch, and preview for opening a print-ready view — all sharing the same underlying configuration.
For teams that need telemetry, the hook is the natural place to log export events, durations, and failures, giving product analytics a single instrumented entry point.
When the same hook is used across many pages, centralizing filename rules there (for example, appending the date) ensures the whole product exports consistently named files.
The hook can also centralize accessibility touches: the export button's busy state and the result announcement are managed in one place, so screen-reader users get consistent feedback on every page that exports.
// hooks/usePdfExport.js
import { useCallback } from 'react';
import { DomPDF } from 'dompdf.js';
export function usePdfExport(filename = 'export.pdf') {
const exportPdf = useCallback((source, options = {}) => {
const pdf = new DomPDF();
pdf.addPage(source, { format: 'A4', margin: '15mm', ...options });
pdf.save(filename);
}, [filename]);
return { exportPdf };
}
// usage: const { exportPdf } = usePdfExport('orders.pdf');
CSS-in-JS solutions such as styled-components and Emotion attach real stylesheets to the DOM, which dompdf.js normally reproduces during snapshot rendering. If anything looks off, first check whether critical styles are inline. CSS-in-JS libraries that inject styles at runtime are captured by the snapshot, but styles that depend on media queries (such as print styles) need explicit handling, since the export runs in the live page context.
Wrap the export region in its own container to keep it free of global layout effects like scroll containers or fixed elements. A4 maps to 794px wide, so matching the container width to the paper gives the most stable pagination. A fixed-width export container also protects against horizontal scrollbars sneaking into the snapshot; overflow content is clipped or paginated instead of stretching the page.
Content that must be exported but not displayed can live in an off-screen or hidden container and be rendered before export, ensuring the DOM is complete. Clean it up after export so it does not disturb the page layout. If a styled component's stylesheet injects after the snapshot begins, the PDF can miss the latest styles; rendering the region, waiting a tick, and then exporting avoids the race.
Charts from libraries like recharts or victory render to SVG, which dompdf.js embeds as vector graphics — but only if the container is visible or force-rendered at export time.
For multi-page reports, set page-break-inside: avoid on cards and chart blocks; the engine then moves whole blocks to the next page instead of slicing a chart in half.
Font loading is a quieter trap: web fonts that arrive after the snapshot produce fallback glyphs, so await document.fonts.ready before exporting a styled report.
When the export region uses CSS modules, the hashed class names are already applied in the DOM, so the snapshot carries the correct styles automatically — one less thing to configure compared with string-based templates.
Q: How do I use it in Next.js? A: dompdf.js relies on browser APIs, so call it inside useEffect or an event handler, never during the server render phase. Dynamic import also trims the initial bundle size. With the Next.js App Router, mark the export component as client-only and invoke the library in event handlers; dynamic import keeps the WASM payload out of the server bundle.
Q: The export region contains Canvas charts — what should I do? A: Convert the canvas to a dataURL image and insert it into the DOM before export. dompdf.js supports image embedding, so charts appear fully in the PDF. Note that canvas captures are resolution-bound: export at devicePixelRatio scale if the chart will be viewed on high-DPI screens.
Q: Is it TypeScript compatible? A: Yes. The package ships its own type declarations, so importing it in a component gives full type hints without installing any @types package. Whatever the framework layer, the rule never changes: browser APIs run client-side, after render, never during the server phase.
Q: Does the exported PDF include CSS animations or transitions? A: No — the snapshot captures the final computed style, so animations settle to their end state; design export templates with static styles in mind.
Q: Can I export a report that is not currently visible, such as a tabbed view's inactive tab? A: Render the hidden tab's content into an off-screen container, export it, then remove it; the hidden container keeps layout intact while the snapshot captures complete content.
Q: How do I handle fonts in the exported PDF? A: The engine embeds the fonts used by the template; for web fonts, await their readiness before export so the PDF uses the intended typeface instead of a fallback.
Q: Can the hook report download progress? A: Yes — the library accepts a progress callback; pass it through the hook's options so the interface can render a progress bar for large documents.
下面的按钮用 dompdf.js 在浏览器端实时生成 PDF,无需后端:
这是由 dompdf.js 渲染的示例 PDF 内容。