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.

01

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.

02

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.

03

Type-safe SDK

`@pdfend/sdk` ships with typed inputs and results. Autocomplete every option, catch bad payloads at build time.

04

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.

05

Dynamic data friendly

Fetch from your database inside the server component, pass props, render — one request per user, per document, per order.

06

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.

case · 01

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.

case · 02

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.

case · 03

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.

case · 04

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 fullhttps:// URLs or base64 data URIs.
  • print-background must be true. By default Chromium omits background colors and images in print mode. Setoptions.printBackground: true to include them.
  • Viewport sizing is A4 by default. Your CSS media queries for print are 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.
FAQ

Frequently asked questions.

01Why not use react-pdf or @react-pdf/renderer?
+
react-pdf is great if your document is simple and you want to avoid a network round-trip. It uses its own layout engine with a limited CSS subset though — no Tailwind, no complex Grid layouts, no Google Fonts auto-loading, no header/footer pagination tokens. PDFend uses real Chromium, so anything you design in the browser prints identically.
02How do I render a React component to HTML on the server?
+
In Next.js use the built-in renderToString from react-dom/server, or return from a server component and pass its rendered output. The PDFend SDK just accepts a plain HTML string — how you produce it is up to you.
03Does it work with CSS-in-JS libraries?
+
Yes. styled-components, emotion, vanilla-extract, Panda — anything that supports SSR works. Extract the critical CSS during render and include it in a <style> tag in your HTML string before sending to PDFend.
04Can I include images from my own domain?
+
Yes. Use absolute URLs (https://cdn.yourapp.com/logo.png) — PDFend's renderer fetches them with the same privileges as a browser. Base64 data URLs work too if you'd rather inline small images.
05Does it work with Remix / Astro / plain React?
+
Yes. Any framework that can render React to HTML on the server works. Remix loaders, Astro server islands, Vite SSR, plain Node scripts — all fine. The PDFend SDK is framework-agnostic.
06How do I handle multi-page documents?
+
Use CSS page-break-before, page-break-after, and break-inside: avoid. We recommend structuring your JSX with one <section> per page and an explicit page-break-after on each, so the layout is predictable.
07Can I generate hundreds of PDFs in parallel?
+
Yes. Pro plans support 20 req/s sustained, Scale 60 req/s. For high-volume jobs, use async mode with webhooks — your process fires off the requests and receives a callback per completion.
08Is it expensive to run on Vercel / Cloudflare Workers?
+
No — calling PDFend is a single fetch request. Your serverless function only runs for a few hundred milliseconds; the actual Chromium render happens in our infrastructure. That's the whole point of using an API instead of running Puppeteer in a Lambda.
Keep reading

Related pieces.

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.