React to PDF
Turn your existing React components into pixel-perfect PDFs. Render the JSX to HTML on the server, hand the string to PDFend, and get a PDF URL back — all in a single server action, route handler, or edge function.
You've already solved the layout problem. Your invoice component renders on a customer's dashboard with the right numbers, the right fonts, and the right spacing. Instead of duplicating that work in a dedicated PDF library with a different CSS subset, reuse the component — server-render it to an HTML string, send it to PDFend, and print it through the same Chromium engine that powers your customer's browser.
Next.js server action example
The idiomatic pattern for Next.js App Router: render the React tree on the server, extract the HTML, call PDFend, return the URL.
// app/actions/generate-invoice.ts
"use server";
import { renderToString } from "react-dom/server";
import { PDFend } from "@pdfend/sdk";
import { Invoice } from "@/components/Invoice";
const pdfend = new PDFend(process.env.PDFEND_API_KEY!);
export async function generateInvoice(orderId: string) {
const order = await db.orders.findById(orderId);
const html = renderToString(<Invoice order={order} />);
const { url } = await pdfend.generate({
html,
options: {
format: "A4",
margin: { top: "20mm", bottom: "20mm" },
printBackground: true,
},
});
return { url };
}Using Tailwind in your PDF
Tailwind works out of the box. Build a minimal Tailwind stylesheet once and inline it in the HTML string, or link to a CDN-hosted bundle — both work. For production we recommend precompiling a dedicatedtailwind-pdf.cssso you're not paying for unused utilities on every render.
import tailwindCss from "@/lib/tailwind-pdf.css?raw";
const body = renderToString(<Invoice order={order} />);
const html = `
<!DOCTYPE html>
<html>
<head>
<style>${tailwindCss}</style>
</head>
<body>${body}</body>
</html>
`;
const { url } = await pdfend.generate({ html });Why PDFend over react-pdf
react-pdf is a popular library for generating PDFs from React, and it shines when your document is small and self-contained. But it has its own layout engine with a limited CSS subset — no Tailwind, no CSS Grid, and no access to Google Fonts. With PDFend you keep React as the authoring layer and Chromium as the rendering layer.
Works with Next.js App Router
Call PDFend from a route handler, a server action, or middleware. No client-side bundle impact — everything happens on the server.
Keeps your design system
Render with your usual component library — Radix, shadcn/ui, Base UI, custom design tokens. Tailwind, CSS modules, CSS-in-JS all render identically to the browser.
Type-safe SDK
`@pdfend/sdk` ships with typed inputs and results. Autocomplete every option, catch bad payloads at build time.
Server-only, zero bundle bloat
React runs on the server via renderToString (or Next's built-in streaming). Your client bundle never imports the PDF engine.
Dynamic data friendly
Fetch from your database inside the server component, pass props, render — one request per user, per document, per order.
Works in edge runtimes
The SDK is dependency-free and built on fetch. Call PDFend from Vercel Edge Functions, Cloudflare Workers, or Deno Deploy.
App Router, Route Handlers, Edge
All three Next.js patterns work. Pick whichever matches your product shape.
Route handler that streams the PDF to the browser
// app/api/invoice/[id]/route.ts
import { renderToString } from "react-dom/server";
import { PDFend } from "@pdfend/sdk";
import { Invoice } from "@/components/Invoice";
const pdfend = new PDFend(process.env.PDFEND_API_KEY!);
export async function GET(req: Request, { params }: { params: { id: string } }) {
const order = await db.orders.findById(params.id);
const html = renderToString(<Invoice order={order} />);
const { url } = await pdfend.generate({ html });
return Response.redirect(url, 302);
}Edge function (Vercel Edge / Cloudflare Workers)
export const runtime = "edge";
import { renderToString } from "react-dom/server.edge";
import { PDFend } from "@pdfend/sdk";
export async function POST(req: Request) {
const { orderId } = await req.json();
const order = await getOrder(orderId);
const html = renderToString(<Invoice order={order} />);
const pdfend = new PDFend(process.env.PDFEND_API_KEY!);
const { url } = await pdfend.generate({ html });
return Response.json({ url });
}What people build
Four patterns dominate React-to-PDF workloads on PDFend. If your use case isn't here, it probably still works — we've seen everything from tax forms to wedding invitations.
Per-user invoices from an existing React invoice component
You already render <Invoice /> in your dashboard. Reuse the component server-side with the customer's data, hand the HTML to PDFend, and email the result.
Pixel-perfect reports with charts
Render chart components (Recharts, Visx, Nivo) server-side to SVG, include them in your PDF page. No canvas screenshots, no rasterization.
Dynamic certificates after course completion
When a user finishes a course, a server action renders a <Certificate /> with their name and a verification code, then PDFend emails them the PDF.
Shareable share-sheet exports
Turn any in-app view into a PDF export — a project summary, a meeting recap, a dashboard snapshot. One click becomes one server action becomes one PDF URL.
Performance notes
The most common bottleneck we see in React-to-PDF pipelines is rendering large CSS bundles on every call. Strip your Tailwind stylesheet to just the utilities the PDF needs (Tailwind's@apply + a dedicated config works well), or inline critical CSS only. A typical single-page invoice renders in 700–1000 ms end-to-end from a Vercel serverless function.
For bulk exports (think “download all 500 invoices from Q1”), use async mode with webhooks. Your API route returns immediately with a job ID list; PDFend POSTs each completed PDF URL to your webhook. You stitch them into a ZIP or a merged document on completion.
Styling gotchas
A few things that are subtly different from browsers and worth knowing up front.
- Images need absolute URLs. The renderer is on our servers, so relative paths resolve to nothing. Use full
https://URLs or base64 data URIs. - print-background must be true. By default Chromium omits background colors and images in print mode. Set
options.printBackground: trueto include them. - Viewport sizing is A4 by default. Your CSS media queries for
printare respected, and the viewport is calculated from the paper format — not from a phone/desktop preset. - Avoid animations.CSS animations and transitions don't render in PDFs. Use static states only.
- SVG charts render natively. Recharts, Visx, any SVG-first chart library prints sharply at any zoom level. Canvas charts need to be exported to an image first.
Frequently asked questions.
01Why not use react-pdf or @react-pdf/renderer?+
02How do I render a React component to HTML on the server?+
03Does it work with CSS-in-JS libraries?+
04Can I include images from my own domain?+
05Does it work with Remix / Astro / plain React?+
06How do I handle multi-page documents?+
07Can I generate hundreds of PDFs in parallel?+
08Is it expensive to run on Vercel / Cloudflare Workers?+
Related pieces.
Everything the /v1/generate endpoint accepts and how it renders.
Every method, option, and error the @pdfend/sdk package exposes.
Prefer server-side templates to server-side React? Store HTML once, render with data.
Let Claude or Cursor generate PDFs from your React components on demand.
Print your React components
Free 100 PDFs per month. Typed SDK. Works with Next.js, Remix, Astro, and any React SSR. Ten minutes from signup to first PDF.