In one sentence
A guide to three ways of turning React components into PDFs, the @react-pdf/renderer library, the browser print dialog and server-side headless Chromium through the AIIPWorld API, with code we ran.
Key facts
- @react-pdf/renderer builds the PDF from its own components (Document, Page, View, Text) and a StyleSheet; it does not take HTML or CSS from the DOM (react-pdf.org, read 2026-10-12).
- window.print() opens the print dialog; the user chooses Save as PDF, and the page cannot save the file itself (MDN, read 2026-10-12).
- With the API, render the component to an HTML string (renderToStaticMarkup from react-dom/server), then POST /v1/render with html and output pdf; the call answers 202 with a job id, and you poll GET /v1/jobs/:id until the status is succeeded, failed or timeout.
- The library example was run on 2026-10-12 with @react-pdf/renderer 4.9.0, React 19.3.0 and Node.js 24.11.1; the API helper was run against the gateway on 2026-10-11.
React to PDF: three ways
React to PDF can mean three different approaches. You can use a PDF library written for React, @react-pdf/renderer (its site is react-pdf.org), which people also call "react pdf." You can use the browser's print dialog and let the user save the file. Or you can render your React component to HTML on your server and send that HTML to headless Chromium through the AIIPWorld render API. Each one fits a different kind of project, so the choice depends on how much control you need, where the code runs, and whether your existing web styles must carry over.
React to PDF with @react-pdf/renderer
@react-pdf/renderer is a React renderer for creating PDF files in the browser and on the server. Install it with npm install @react-pdf/renderer. Its layout engine, Yoga, brings flexbox to the page, including rows, columns, wrapping and absolute positioning. The same document can render to a file, a stream or a blob, in Node or in the client.
The main requirement to understand is that you write the document with its own components: Document, Page, View and Text, plus a StyleSheet. It does not take HTML or CSS from the DOM. Your existing web components, such as div, table or Tailwind classes, cannot be passed in as they are. If your design already lives in HTML and CSS, you will need to rebuild the layout in react-pdf's components.
On Node, the library provides three helpers. renderToFile(<MyDocument />, path) writes a file. renderToStream(<MyDocument />) returns a stream. renderToBuffer(<MyDocument />) returns a Node Buffer. All three take the root Document element.
Fonts work in a specific way. Courier, Helvetica and Times-Roman work out of the box. You can add more with Font.register, but only TTF and WOFF font files are supported, and variable fonts do not work properly. PDF does not support colour emoji fonts, so react-pdf can fetch emoji images from a CDN with Font.registerEmojiSource. That option needs an internet connection at render time. Hyphenation is set with Font.registerHyphenationCallback.
This approach fits when you want the PDF generated from your own code, with no browser and no external service, and you are willing to build the layout with react-pdf's components. The example below is the file we ran.
import React from "react";
import { Document, Page, Text, View, StyleSheet, renderToFile } from "@react-pdf/renderer";
const h = React.createElement;
// react-pdf does not read HTML or CSS. It has its own components and a StyleSheet with flexbox layout.
const styles = StyleSheet.create({
page: { padding: 36, fontFamily: "Helvetica", fontSize: 11 },
title: { fontSize: 20, marginBottom: 12 },
row: { flexDirection: "row", borderBottom: "1px solid #dddddd", paddingVertical: 4 },
item: { flexGrow: 1 },
num: { width: 70, textAlign: "right" },
});
const rows = [
{ item: "Design review", qty: 2, price: 120 },
{ item: "Hosting, one month", qty: 1, price: 35.5 },
{ item: "Support hours", qty: 5, price: 60 },
];
const Invoice = () =>
h(
Document,
null,
h(
Page,
{ size: "A4", style: styles.page },
h(Text, { style: styles.title }, "Invoice 1001"),
...rows.map((r) =>
h(
View,
{ key: r.item, style: styles.row },
h(Text, { style: styles.item }, r.item),
h(Text, { style: styles.num }, String(r.qty)),
h(Text, { style: styles.num }, `$${r.price.toFixed(2)}`),
),
),
),
);
await renderToFile(h(Invoice), "invoice-react-pdf.pdf");
console.log("written");We ran this file on 2026-10-12 with @react-pdf/renderer 4.9.0, react 19.3.0 and Node.js 24.11.1. It used renderToFile and wrote a one-page A4 PDF.
React to PDF with the browser print dialog
The simplest option is window.print(). This method, described in MDN's documentation, opens the print dialog to print the current document. Your code does not need a server, a library or an extra service. The user then chooses "Save as PDF" or a printer in the dialog.
That simplicity comes with limits. The page cannot save the file by itself, because the user makes that choice in the dialog. The call also blocks while the dialog is open. The layout you get depends on the user's browser and on their print dialog settings, so you have less control over the final file than with the other two options.
Print styles are plain CSS, using @media print and @page. For a full walkthrough, see print CSS for PDF.
If you later want to send the already rendered page to the API, you can, but styles loaded from your own build must be inlined or reachable at a public address.
React to PDF with server-side Chromium
The third option reuses the HTML and CSS you already have. Your React component is rendered to an HTML string on your server. You can use renderToStaticMarkup from react-dom/server, or any framework's server render. That HTML string is then sent to POST /v1/render on api.aiipworld.com with output set to pdf. Headless Chromium prints the page, so normal CSS such as flexbox, grid and web fonts loaded from a public URL works.
The request returns 202 with a job_id. There is no synchronous mode, so your code polls GET /v1/jobs/:id until the status is succeeded, failed or timeout. A succeeded job includes a download_url, which is a short-lived signed link. Anyone with that link can download the file without an API key. Credits for failed and timed-out jobs are returned. Authentication uses an API key from the dashboard in the Authorization: Bearer <key> header, or in the x-api-key header.
For a step-by-step client that submits, waits and downloads, see HTML to PDF with Node.js. The example below turns an Invoice component into a full HTML document string. We ran this HTML step on 2026-10-12 with react-dom 19.3.0, and the output starts with <!doctype html>. The file imports the client helper from the same guide, and it needs an API key in AIIPWORLD_API_KEY.
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { render } from "./aiipworld-pdf.mjs";
const h = React.createElement;
const css = `
body { font-family: "Liberation Sans", Arial, sans-serif; margin: 0; }
h1 { font-size: 22px; }
table { border-collapse: collapse; width: 100%; }
th, td { border-bottom: 1px solid #ddd; padding: 6px 8px; text-align: left; }
td.num, th.num { text-align: right; }
tr { break-inside: avoid; }
`;
// An ordinary React component. renderToStaticMarkup turns it into an HTML string on the server.
function Invoice({ number, rows }) {
const total = rows.reduce((sum, r) => sum + r.qty * r.price, 0);
return h(
"html",
{ lang: "en" },
h("head", null, h("meta", { charSet: "utf-8" }), h("style", null, css)),
h(
"body",
null,
h("h1", null, `Invoice ${number}`),
h(
"table",
null,
h("thead", null, h("tr", null, h("th", null, "Item"), h("th", { className: "num" }, "Qty"), h("th", { className: "num" }, "Price"))),
h(
"tbody",
null,
rows.map((r) =>
h("tr", { key: r.item }, h("td", null, r.item), h("td", { className: "num" }, r.qty), h("td", { className: "num" }, `$${r.price.toFixed(2)}`)),
),
),
),
h("p", null, `Total: $${total.toFixed(2)}`),
),
);
}
const rows = [
{ item: "Design review", qty: 2, price: 120 },
{ item: "Hosting, one month", qty: 1, price: 35.5 },
{ item: "Support hours", qty: 5, price: 60 },
];
const html = "<!doctype html>" + renderToStaticMarkup(h(Invoice, { number: "1001", rows }));
// The same HTML string goes to the render API; headless Chromium prints it.
const job = await render({ html, output: "pdf", options: { format: "A4" } }, "invoice-1001.pdf");
console.log(job.status, job.result.pages, "page(s)");The helper used in the examples is aiipworld-pdf.mjs, which submits the job, waits for it and downloads the result. The example sends the HTML string. A public url can be used instead when the page is running at a public address.
PDF options include format (A4 is the default), landscape, margin (10 mm by default), print_background (on by default), page_ranges, and header and footer templates with page numbers through spans with the classes pageNumber and totalPages. The option prefer_css_page_size lets your CSS page size take effect. JavaScript runs in the rendered page by default. For a full list of the API fields, see the PDF API page and the API documentation.
Pitfalls when converting React pages to PDF
Several problems come up in every approach. These are the ones to check first.
- Client-side data fetching is not run. renderToStaticMarkup renders the tree once on the server and does not run client-side fetches inside components. Fetch the data before rendering and pass it in as props, or send a public URL of your running app instead of HTML.
- Styles must be inlined or public. Chromium cannot read CSS files that exist only on your build machine. Inline the CSS in the HTML, or make the stylesheet reachable at a public address.
- Login pages show the login screen. A page behind a login shows its login screen to the render service. There is no cookie option. Send the HTML instead of a URL, or use a public address.
- Fonts on the render server are limited. The render server includes Liberation, Noto Sans and Serif, Noto CJK, Noto Color Emoji and many other Noto scripts. Fonts on your own machine are not available. Load any other font with @font-face from a public URL or a data URL.
- Page breaks need CSS. Use break-inside to keep rows, cards or blocks from splitting across pages.
- Suspense fallbacks are sent as HTML. renderToStaticMarkup has limited Suspense support. If a component suspends, its fallback is what appears in the output. Check the rendered HTML before you send it.
- react-pdf needs its own components. If you expect your web markup to carry over, you will need to rewrite the layout.
Which one to choose
Choose the option that matches what you need to control and where the work should run.
- @react-pdf/renderer builds the PDF from react-pdf components and runs in Node or the browser. It does not take HTML or CSS as input, and its fonts are limited to the ones you register. It suits projects that already accept rebuilding layouts in its components.
- Browser print needs no server and no extra service. The user does the saving, and the layout depends on the user's browser and print dialog settings. It suits simple, user-driven exports.
- Chromium through the API reuses the HTML and CSS you already have. It needs a server-side request, an API key and credits. It suits teams whose layouts already live in web code and who want the server to produce the file.
We have not compared speed or output quality between these options, so this guide does not rank them on those points. Each option has its own strengths, and the right one depends on your layout, your users and your infrastructure.
If you want to test a page before writing code, the HTML to PDF tool works in the browser with no account. For plan details and credit terms, see pricing.
Questions
What is react pdf?
React PDF is the name commonly used for @react-pdf/renderer, a React renderer for creating PDF files in the browser and on the server. You install it with npm install @react-pdf/renderer and write the document with its own components, Document, Page, View and Text, plus a StyleSheet.
Can I convert my existing React web page to PDF with @react-pdf/renderer?
Not directly. @react-pdf/renderer does not take HTML or CSS from the DOM, so your web components, such as div, table or Tailwind classes, cannot be passed in as they are. You write the document with its own components. If you want to keep your existing HTML and CSS, render the page to HTML on the server and send it to the render API.
Does window.print() save a PDF file automatically?
No. window.print() opens the print dialog, and the user chooses "Save as PDF" or a printer. The page cannot save the file by itself. The call also blocks while the dialog is open.
Do I need a server to make a PDF from React?
Not with @react-pdf/renderer or the browser print dialog. @react-pdf/renderer runs in Node or in the browser, and window.print() runs in the user's browser. Sending HTML to the render API needs a server-side request, because you render the component to an HTML string on your server and then call POST /v1/render.
Why does my PDF show a login screen instead of my page?
The render service cannot use your cookies. If the page is behind a login, the service sees the login screen. Send the finished HTML in the request instead of a URL, or use a public address that does not require login.
Why are my fonts missing in the PDF?
Fonts on your own machine are not available on the render server. The server includes Liberation, Noto and Noto CJK fonts, among others. For any other font, load it with @font-face from a public URL or a data URL. In @react-pdf/renderer, fonts must be TTF or WOFF files registered with Font.register, and variable fonts do not work properly.
How does the render API return my PDF?
POST /v1/render returns 202 with a job_id. Your code then polls GET /v1/jobs/:id until the status is succeeded, failed or timeout. A succeeded job includes a download_url, a short-lived signed link that does not need an API key. There is no synchronous mode.