Skip to content

§ Next.js PDF generator

Next.js PDF Generator: Generate a PDF from HTML in Next.js

Generate a PDF from an HTML page or React component in your Next.js app with one HTTP call. Managed Chromium renders your real markup on the server, so you get a true, selectable PDF without bundling headless Chrome into a serverless function that will not fit. Try it on any URL below.

  • Early access, launching soon
  • No card required
  • Your HTML stays yours

§ Live demo

Runs in your browser. Nothing is uploaded.

Your PDF is downloading. Want this as one API call, with the page archived too?

§ Short answer

The blocker for a Next.js PDF generator is not writing the code, it is the runtime. Next.js has no built in PDF export, and on Vercel or any serverless host you cannot reliably launch full Puppeteer, the Chromium binary is far larger than the function size limit. Teams end up either wrestling @sparticuz/chromium into a function and fighting cold starts and size caps, or calling a rendering API that runs Chromium elsewhere. The API path is a single fetch from a route handler, returns a true vector PDF with selectable text, and works the same on Vercel, a Node server, or the edge.

Last updated July 2026. Written and fact checked by the Sitepdf team.

§ 00

Four ways to make a PDF in Next.js, compared

They differ most in whether you can run them on serverless without hitting the function size limit, and whether the PDF contains real, selectable text.

Approach Runs on Vercel serverless Output What you operate Best for
puppeteer (full) No: Chromium exceeds the function size limit True vector PDF A Chromium install, only on a long lived Node server A self-hosted Next.js app on your own server
puppeteer-core + @sparticuz/chromium Barely: fits the cap but fragile, slow cold starts True vector PDF A pinned chromium build you upgrade by hand Teams committed to keeping Chromium inside the function
@react-pdf/renderer, client jsPDF Yes jsPDF is a raster snapshot; react-pdf is real but its own layout Nothing, but you rebuild the layout in JSX A bespoke document, not a copy of an existing page
Rendering API
Sitepdf
Yes, it is just a fetch True searchable vector PDF, print accurate Nothing, no browser in your deploy Modern CSS at volume on serverless, plus a dated record

The honest read: if your Next.js app runs on a long lived Node server you control, full Puppeteer works fine and is free, so use it. The pain is specific to serverless, where the Chromium binary does not fit and cold starts hurt. That is exactly where offloading the browser to an API pays for itself.

§ 01

Why headless Chrome is the hard part on Vercel

Almost every Next.js PDF tutorial starts with Puppeteer, and almost every one of them assumes a normal Node server. On Vercel and similar serverless hosts that assumption breaks. A serverless function has a hard size limit, and a full Chromium download is far larger, so npm install puppeteer and deploy simply fails to fit. The community workaround is puppeteer-core paired with @sparticuz/chromium, a Chromium build stripped down and compressed specifically to squeeze under the cap.

It can work, and for some teams it is worth it, but be clear eyed about the cost. You are now pinning two packages whose versions must stay compatible with each other and with the platform, every upgrade is a small risk, and each cold start has to decompress and boot Chromium, which adds noticeable latency to the first request after idle. Our Puppeteer PDF on AWS Lambda guide walks through this exact setup and its failure modes on Lambda, and the same constraints apply on Vercel functions.

§ 02

The API path: one fetch from a route handler

The alternative is to never put a browser in your deploy at all. A rendering API runs managed Chromium on its own servers, so from Next.js it is a single request. This works identically on a Vercel function, the edge runtime, or a self-hosted Node server, because nothing about it depends on your runtime being able to launch a browser.

// app/api/pdf/route.ts
export async function POST(req: Request) {
  const { url } = await req.json();

  const res = await fetch("https://api.sitepdf.com/v1/render", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SITEPDF_KEY}` },
    body: new URLSearchParams({ url, format: "Letter", archive: "true" }),
  });

  const { pdf_url } = await res.json();
  return Response.json({ pdf_url });
}

Point it at any route in your app, an invoice page, a receipt, a dashboard export, and Chromium loads that page exactly as a visitor would, runs your client JavaScript, waits for charts, and returns the PDF. If the document is a React component you would rather not expose as a route, render it to an HTML string on the server with renderToStaticMarkup and post the HTML instead, which the React to PDF page covers in full.

§ 03

When to skip the API

We will say plainly when you do not need us. If your Next.js app is deployed on a long lived Node server you control, a VPS, a container, a traditional host, then full puppeteer installs and runs without any size limit, and it is free. Run it yourself. The Puppeteer alternative page lays out that trade off honestly: self-hosting Chromium is the right call when you have somewhere to run it and someone to keep it patched and memory stable under load.

The API earns its place in two situations. The first is serverless, where the browser genuinely does not fit and every cold start costs you. The second is scale, where keeping a fleet of Chromium processes warm, patched and from leaking memory becomes real operational work you would rather not own. If neither applies to you, a local browser is a perfectly good Next.js PDF generator.

§ 04

Keep a dated copy of every PDF you generate

One capability no local approach offers is a record. When your Next.js app generates an invoice or a signed quote, the file ships to the customer and nothing verifiable stays behind. Add archive=true to the render call and Sitepdf stores a timestamped, immutable snapshot of exactly what it rendered, retrievable by id. For finance and compliance features that provenance is frequently the whole reason to render server side. The website archiving page explains the record, and the PDF generator API page has every parameter. Renders are transient by default; leave archiving off and we store nothing.

From a Next.js route handler: render a page in managed Chromium and keep a dated copy.
curl https://api.sitepdf.com/v1/render \
  -H "Authorization: Bearer $SITEPDF_KEY" \
  -F url=https://app.example.com/invoices/8842 \
  -F format=Letter \
  -F print_background=true \
  -F archive=true

{
  "pdf_url": "https://api.sitepdf.com/v1/documents/doc_4c8pn.pdf",
  "pages": 2,
  "rendered_in_ms": 1755,
  "archive": {
    "id": "arc_q92wl",
    "captured_at": "2026-07-19T18:58:33Z",
    "retrieve_url": "https://api.sitepdf.com/v1/archives/arc_q92wl"
  }
}

The API is in early access; this is the documented call shape it opens with. Full request and response walkthrough.

§ 05

Questions about this job

How do I generate a PDF in Next.js?
From a route handler, either post the HTML you rendered with renderToStaticMarkup or pass the URL of a page in your app to a rendering API that runs Chromium on its servers. You get back a true vector PDF with selectable text in one fetch. On a self-hosted Node server you can instead run full Puppeteer, but that does not fit inside a serverless function.
Can I run Puppeteer on Vercel for PDF generation?
Not full Puppeteer: the bundled Chromium is far larger than the Vercel function size limit, so the deploy fails. The workaround is puppeteer-core with @sparticuz/chromium, a stripped-down Chromium that fits, but it is fragile to upgrade and adds cold-start latency. Offloading rendering to an API avoids putting a browser in the function at all.
What is the best way to make a PDF from HTML in a Next.js API route?
On serverless, call a rendering API from the route handler with a single fetch, passing either your HTML or a page URL. On a long-lived Node server you control, full Puppeteer works and is free. Client-side html2canvas or jsPDF should be avoided for real documents because they embed an image, so the text is not selectable.
Does the API work with the Next.js edge runtime?
Yes. Because the call is a plain HTTP request and no browser launches in your function, it runs the same on the edge runtime, on a standard Vercel serverless function, and on a self-hosted Node server. That portability is the main reason teams pick an API over bundling Chromium, which only runs in specific environments.
How do I keep a copy of the PDFs my Next.js app creates?
Local rendering hands you the file and keeps no record. A rendering API with an archive option stores a timestamped snapshot of each render, retrievable by id, so you have a dated copy of exactly what an invoice or statement looked like when you produced it. That provenance is often why finance and compliance teams render server side.

§ Early access

Get on the early-access list

The API opens to the list first, in order. Early access locks the planned launch rates for 12 months. No card required, launching soon.

Render + archive, one API