PDF generation from Sanity data with NextJS - tool recommendations?
There are several solid approaches for generating PDFs from Sanity data in Next.js, and I'll walk you through the most popular options the community uses.
The Main Approaches
1. HTML-to-PDF with Puppeteer (Most Common)
Since you already have HTML rendering, using Puppeteer or Playwright to convert HTML to PDF is probably your best bet. For Next.js on Vercel specifically, you'll want to use:
puppeteer-core(instead of full Puppeteer)@sparticuz/chromiumor@sparticuz/chromium-min(provides the Chrome binary for serverless environments)
Here's a typical setup in a Next.js App Router route handler (app/api/generate-pdf/route.ts):
import puppeteer from 'puppeteer-core';
import chromium from '@sparticuz/chromium';
export async function POST(request: Request) {
const { slug } = await request.json();
const browser = await puppeteer.launch({
args: chromium.args,
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath(),
headless: chromium.headless,
});
const page = await browser.newPage();
await page.goto(`${process.env.NEXT_PUBLIC_URL}/your-content/${slug}`, {
waitUntil: 'networkidle0',
});
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
});
await browser.close();
return new Response(pdf, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="${slug}.pdf"`,
},
});
}Check out the @sparticuz/chromium documentation for Vercel-specific configuration details and the Vercel docs on using Puppeteer.
2. React-PDF or PDFKit (Direct Generation)
If you want more control over PDF layout or don't want the overhead of running a headless browser, consider:
- @react-pdf/renderer - Write React components that render to PDF
- PDFKit - Lower-level PDF generation
With React-PDF, you'd create PDF-specific components and render your Portable Text or Sanity data directly:
import { Document, Page, Text, View } from '@react-pdf/renderer';
import { pdf } from '@react-pdf/renderer';
const MyDocument = ({ sanityData }) => (
<Document>
<Page>
<View>
<Text>{sanityData.title}</Text>
{/* Map your Sanity content here */}
</View>
</Page>
</Document>
);
// In your route handler
const pdfBlob = await pdf(<MyDocument sanityData={data} />).toBlob();3. Third-Party Services
Services like DocRaptor, PDFShift, or Gotenberg can handle the heavy lifting if you don't want to manage Chrome binaries in serverless environments.
Which Approach to Choose?
Go with Puppeteer if:
- You already have well-styled HTML pages
- You want pixel-perfect PDFs matching your web design
- You're okay with the cold-start overhead and larger function sizes
Go with React-PDF if:
- You need precise control over PDF layout
- You want smaller serverless functions
- Your PDF design differs significantly from your web design
- You're generating lots of PDFs and want better performance
Go with a service if:
- You want to avoid managing Chrome binaries
- You need advanced PDF features (forms, annotations, etc.)
- Budget allows for per-PDF costs
Handling Portable Text
If your Sanity data includes Portable Text, you'll need to serialize it. With Puppeteer, you can use @portabletext/react on your HTML page. With React-PDF, you'll need to write a custom serializer since React-PDF uses its own component system.
Sanity Functions Alternative
Worth mentioning: you could also use Sanity Functions to generate PDFs as part of your content workflow (e.g., auto-generate a PDF when a document is published). This keeps the compute workload inside Sanity's infrastructure and can be triggered by content changes. However, for on-demand user-initiated PDF generation in your Next.js app, a route handler is more straightforward.
The Puppeteer + @sparticuz/chromium approach on Vercel is probably your quickest path to success since you already have HTML rendering set up!
Show original thread1 reply
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.