Appearance
Working App Router Route Handler code (sync and direct), no Puppeteer on your server—get your first PDF in minutes with copy-paste examples.

Generating PDF in Next.js is a common need: invoices, reports, exports, or any document your app must deliver as a file. Doing it well means choosing the right approach and having production-ready code.
This guide gives you exactly that: why PDF in Next.js often leads to an API instead of Puppeteer, then working App Router Route Handler examples for both sync and direct PDF generation, with correct response handling and env-based API keys. You’ll also see when to use sync vs direct vs async and where to go next.
Next.js is a natural fit for PDF generation: you can run server-side logic in API routes or Route Handlers, render HTML (or pass a URL), and return a file or a link. Use cases include on-demand reports, invoice downloads, or exporting dashboard data—all from the same stack your app already uses.
One option is to run Puppeteer (or Playwright) inside a Next.js API route or Route Handler. You get full Chrome rendering, but you pay for it: cold starts, memory limits in serverless (e.g. Vercel), and the burden of installing and maintaining Chrome/Chromium. At scale, many concurrent PDF jobs can exhaust memory and slow down your app.
If you’re considering Puppeteer, read our Puppeteer vs Doppio comparison to see how a managed API avoids these issues while keeping the same rendering quality.
Using an HTML-to-PDF API like Doppio moves Chrome off your infrastructure: you send a URL or HTML, and get back a PDF (or a link to it). No Chrome to install, no scaling or memory worries. Our Next.js quick start shows the same flow in a compact form; for a broader picture of options in 2026, see HTML to PDF in 2026: what are your options?.
Create a Route Handler in the App Router, e.g. app/api/pdf/route.ts. Store your Doppio API key in .env.local as DOPPIO_API_KEY and read it with p.
The sync endpoint returns JSON with a documentUrl (and related fields). Use it when you need to store the URL, send it in an email, or process it further.
// app/api/pdf/route.ts
import { NextResponse } from 'next/server';
export async function POST() {
const apiKey = process.env.DOPPIO_API_KEY;
if (!apiKey) {
return NextResponse.json({ error: 'Missing DOPPIO_API_KEY' }, { status: 500 });
}
const response = await fetch('https://api.doppio.sh/v1/render/pdf/sync', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
page: {
pdf: { printBackground: true },
goto: {
url: 'https://example.com/your-page',
options: { waitUntil: ['networkidle0'] },
},
},
}),
});
const data = await response.json();
return NextResponse.json(data);
}
The direct endpoint returns the PDF binary. You must stream the response body back with the right headers—do not call response.json().
// app/api/pdf/route.ts
import { NextResponse } from 'next/server';
export async function POST() {
const apiKey = process.env.DOPPIO_API_KEY;
if (!apiKey) {
return NextResponse.json({ error: 'Missing DOPPIO_API_KEY' }, { status: 500 });
}
const response = await fetch('https://api.doppio.sh/v1/render/pdf/direct', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
page: {
pdf: { printBackground: true },
goto: {
url: 'https://example.com/your-page',
options: { waitUntil: ['networkidle0'] },
},
},
}),
});
if (!response.ok) {
const err = await response.text();
return NextResponse.json({ error: err }, { status: response.status });
}
return new Response(response.body, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="document.pdf"',
},
});
}
For full request/response details, see render PDF direct and render PDF sync.
For batch jobs or when you don’t want to block the request until the PDF is ready, use the async endpoint with a webhook: you get an immediate response, and Doppio calls your webhook when the file is ready. See render PDF async and full async workflow.
Use the Next.js quick start for more examples and tabs. To avoid layout and rendering pitfalls, read HTML to PDF common pitfalls. For a high-level comparison of all options (wkhtmltopdf, Puppeteer, APIs), see HTML to PDF in 2026: what are your options?.