HTML to PDF
in React.

Most React PDF advice ends with html2canvas and jsPDF, which photographs the DOM and pastes the picture into a PDF. The text is not selectable, it is not searchable, and it prints badly. There is a better shape.

The call

Post your markup with an API key. The PDF comes back in the same response, base64 encoded by default. Nothing is written to disk on our side.

app/api/pdf/route.ts
// Server side. The key never reaches the browser.
export async function POST(request: Request) {
  const { html } = await request.json()

  const res = await fetch("https://api.html4pdf.com/generate-pdf", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HTML4PDF_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ html, responseFormat: "binary" }),
  })

  if (!res.ok) return new Response("Could not render", { status: 502 })

  return new Response(res.body, {
    headers: {
      "Content-Type": "application/pdf",
      "Content-Disposition": 'attachment; filename="invoice.pdf"',
    },
  })
}

The button in your component

Render the markup you want in the PDF with renderToStaticMarkup, or keep a template on the server and post only the data. Either way the browser never sees the key.

DownloadButton.tsx
import { renderToStaticMarkup } from "react-dom/server"

const DownloadButton = ({ invoice }: { invoice: Invoice }) => {
  const download = async () => {
    const html = renderToStaticMarkup(<InvoiceSheet invoice={invoice} />)

    const res = await fetch("/api/pdf", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ html }),
    })

    const url = URL.createObjectURL(await res.blob())
    Object.assign(document.createElement("a"), { href: url, download: "invoice.pdf" }).click()
    URL.revokeObjectURL(url)
  }

  return <button onClick={download}>Download PDF</button>
}

What comes back

The default response is JSON with the document base64 encoded, the credits you have left, and how long the render took. Send responseFormat as binary instead and the body is the PDF itself, with a Content-Type of application/pdf.

200 OK
{
  "success": true,
  "pdf": "JVBERi0xLjQKJeLjz9MKMyAwIG9...",
  "creditsRemaining": 9,
  "metadata": { "size": 42813, "processingTime": 2841 }
}

A 402 means you are out of credits, a 401 means the key is wrong, and a 422 means the markup could not be rendered. Everything else is worth retrying once.

The usual React libraries

Worth knowing before you add a dependency, because for a lot of documents one of these is the right answer and costs nothing.

LibraryWhat it isThe trade-off
html2canvas with jsPDFScreenshots the DOM to a canvas and embeds the image in a PDF.Runs entirely client side. The result is a picture: no selectable text, no working links, blurry when printed, and page breaks fall wherever the image happens to split.
@react-pdf/rendererA React renderer with its own layout engine and PDF primitives.Real text and real vectors, and it works in the browser. You write its components, not HTML, and only a subset of flexbox is supported, so you maintain a second design.
window.print()The browser's own print dialog with a print stylesheet.Free and perfectly good for user-initiated printing. You cannot capture the file or email it.

React: common questions

Can I call a PDF API directly from React?

You can, but do not. Anything in the browser bundle is public, including your API key. Proxy the call through your own backend route, as in the example above.

Why not use html2canvas and jsPDF?

Because the output is an image of your page. Text cannot be selected, searched or copied, links stop working, and anything taller than one page splits mid-line.

How do I turn a React component into a PDF?

Render it to an HTML string with renderToStaticMarkup, send that string to your server route, and have the server call the renderer. The styles have to be inline or in a style tag, since the PDF renderer has no access to your bundled CSS.

What about Tailwind classes in the PDF?

Include a compiled stylesheet in a style tag inside the HTML you send, or use a link tag with an absolute URL. Class names alone mean nothing to a fresh browser page.

Does @react-pdf/renderer need a server?

No, which is its advantage. The trade-off is maintaining a separate component tree for the document.