How to Generate a PDF File from React Components Using react-to-pdf

You can generate PDF files from React components by using the third-party library react-to-pdf, which captures DOM nodes via React refs and rasterizes them using html2canvas and jsPDF without requiring modifications to React's core rendering engine.

React is a UI rendering library that outputs to the browser's DOM, which means it does not include built-in PDF generation capabilities. By leveraging the facebook/react repository's standard rendering pipeline—specifically the client-side mounting logic in packages/react-dom/src/client/ReactDOM.js—you can obtain DOM node references that third-party libraries convert to PDF documents. This approach works because React separates rendering (handled by react-dom) from output formats, allowing any library capable of capturing HTML to work with React components.

How react-to-pdf Integrates with React's Architecture

React's reconciler commits component output to regular DOM nodes through packages/react-reconciler/src/ReactFiberReconciler.js. Once mounted, these nodes are standard browser elements accessible via the ref system implemented in packages/react/src/ReactHooks.js.

The react-to-pdf library exploits this architecture through a three-step process:

  1. DOM Node Capture – Uses React's useRef hook to obtain a reference to the underlying DOM element.
  2. Rasterization – Passes the DOM node to html2canvas to create a bitmap representation.
  3. PDF Generation – Feeds the canvas data into jsPDF to produce a downloadable PDF blob.

Because this process operates entirely on the DOM nodes produced by packages/react-dom/src/client/ReactDOM.js, it functions independently of React's reconciliation, state updates, and concurrent mode features.

Implementation Examples

Basic Component Usage

The most straightforward approach uses the ReactToPdf component wrapper to automatically handle PDF generation when triggered.

import React, {useRef} from "react";
import ReactToPdf from "react-to-pdf";

export default function Invoice() {
  const ref = useRef(); // Reference to the component we want to print

  return (
    <div>
      <div ref={ref} style={{padding: 20, background: "#f7f7f7"}}>
        <h1>Invoice #12345</h1>
        <p>Date: 2024-04-01</p>
        <table>
          <thead>
            <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
          </thead>
          <tbody>
            <tr><td>Widget A</td><td>2</td><td>$20</td></tr>
            <tr><td>Widget B</td><td>1</td><td>$15</td></tr>
          </tbody>
        </table>
        <h2>Total: $55</h2>
      </div>

      <ReactToPdf 
        targetRef={ref} 
        filename="invoice.pdf" 
        options={{orientation: "portrait", unit: "pt", format: "a4"}}
      >
        {({toPdf}) => (
          <button onClick={toPdf} style={{marginTop: 20}}>
            Download PDF
          </button>
        )}
      </ReactToPdf>
    </div>
  );
}

The targetRef={ref} prop tells react-to-pdf which DOM node to capture. When toPdf() is invoked, the library internally accesses the DOM node reference established by React's hook system in packages/react/src/ReactHooks.js.

Hook-Based API for Modern React

For applications requiring more control over the PDF generation lifecycle, use the useReactToPdf hook.

import React, {useRef, useState} from "react";
import {useReactToPdf} from "react-to-pdf";

export default function Report() {
  const ref = useRef();
  const [pdfUrl, setPdfUrl] = useState(null);

  const generatePdf = useReactToPdf({
    targetRef: ref,
    filename: "report.pdf",
    onSuccess: (blob) => {
      const url = URL.createObjectURL(blob);
      setPdfUrl(url);
    },
  });

  return (
    <div>
      <section ref={ref} style={{fontFamily: "sans-serif"}}>
        <h1>Quarterly Report</h1>
        <p>Revenue increased by 24% this quarter.</p>
      </section>

      <button onClick={generatePdf}>Create PDF</button>

      {pdfUrl && (
        <a href={pdfUrl} download="report.pdf" style={{marginLeft: 10}}>
          Download Link
        </a>
      )}
    </div>
  );
}

This method returns a Promise-compatible handler that preserves React's async UI patterns while accessing the same underlying DOM node references.

Server-Side Rendering with Client-Side PDF Generation

When using ReactDOMServer for server-side rendering, you can hydrate the application on the client and then generate the PDF.

// server.js (Node)
import {renderToString} from "react-dom/server";
import Invoice from "./Invoice";

app.get("/invoice/:id", (req, res) => {
  const html = renderToString(<Invoice id={req.params.id} />);
  res.send(`
    <!doctype html>
    <html>
      <head><title>Invoice</title></head>
      <body>
        <div id="root">${html}</div>
        <script src="/bundle.js"></script>
      </body>
    </html>
  `);
});

The server-side rendering logic resides in packages/react-dom/src/server/ReactDOMServer.js and provides the initial HTML string. Once the client bundle hydrates the component using ReactDOM.hydrate (from packages/react-dom/src/client/ReactDOM.js), the react-to-pdf button functions exactly as in the previous examples, generating the PDF entirely within the browser.

Key Source Files in the React Repository

Understanding where React creates and manages DOM nodes helps clarify why external PDF libraries work seamlessly:

These files demonstrate that once React commits a component to the DOM, the resulting nodes are standard browser elements compatible with any HTML-to-PDF conversion tool.

Summary

  • React has no built-in PDF capability – it renders to DOM nodes via packages/react-dom/src/client/ReactDOM.js, requiring third-party libraries for PDF output.
  • react-to-pdf captures DOM references – it uses React's useRef hook (implemented in packages/react/src/ReactHooks.js) to access underlying HTML elements.
  • Rasterization happens client-side – the library uses html2canvas and jsPDF to convert DOM nodes to PDF without server interaction.
  • No core modifications needed – the PDF generation process operates outside React's reconciler (packages/react-reconciler/src/ReactFiberReconciler.js), ensuring compatibility with concurrent mode and future React updates.

Frequently Asked Questions

Does React include built-in PDF generation functionality?

No. React is strictly a UI rendering library that outputs to the browser's DOM or server-side HTML strings through packages/react-dom/src/server/ReactDOMServer.js. PDF generation requires external libraries like react-to-pdf that capture the DOM nodes React produces and convert them to PDF format using canvas rasterization or print-to-PDF APIs.

How does react-to-pdf access content inside React components?

The library leverages React's ref system implemented in packages/react/src/ReactHooks.js. By passing a ref to a component's container element, react-to-pdf obtains a direct reference to the underlying DOM node once React commits it to the browser through packages/react-dom/src/client/ReactDOM.js. This node is then passed to html2canvas for bitmap creation.

Can I generate PDFs on the server with React?

Not directly using react-to-pdf. The library requires browser APIs (html2canvas and jsPDF) to function. However, you can use ReactDOMServer.renderToString from packages/react-dom/src/server/ReactDOMServer.js to render components to HTML on the server, send the markup to the client, hydrate it with ReactDOM.hydrate, and then trigger react-to-pdf on the client side to generate the PDF from the hydrated DOM nodes.

What are the limitations of client-side PDF generation?

Client-side PDF generation depends on the browser's capabilities for rendering CSS and canvas operations. Complex CSS layouts, web fonts, and cross-origin images may not render correctly in the PDF. Additionally, large documents can cause performance issues since the rasterization happens in the main JavaScript thread, potentially blocking React's reconciler updates temporarily.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s https://instagit.com/install.md

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client