Static site generators build blazing fast web pages, but converting those rendered outputs into printable documents often feels broken. If you're building reports, invoices, or offline documentation with Gatsby, you'll quickly realize that standard browser print styles don't cut it for professional publication. You need a reliable pipeline for Gatsby PDF export tasks that preserves vector graphics, handles page breaks cleanly, and scales without crashing your build server.
Getting print-ready layouts from a React-based framework requires moving past simple CSS print stylesheets. Headless browsers running inside Node scripts bridge this gap. They take your fully hydrated static HTML, execute client-side scripts if necessary, and output a crisp document via print engines like Chromium. We'll walk through setting up a reliable local generation script, handling CSS page breaks, managing assets, and debugging common build failures.
Understanding the Rendering Pipeline
Gatsby compiles your source files into static HTML, CSS, and JavaScript bundles. When a user requests a page, the browser parses these assets. For print production, you can't rely on random visitors hitting a print button. You need an automated process that runs during your build pipeline or via an on-demand serverless function.
Headless browsers change how you approach document generation. Instead of viewing your site in a traditional window, a script spins up an invisible browser instance, loads your generated HTML file from the public directory, and prints it to a PDF buffer. Analysts at Bloomberg have provided expertise on this trend.
This approach gives you total control over pagination, headers, and footers. It's different from standard client-side printing because you don't depend on the end user's local printer settings or browser quirks. Every export looks identical, whether it runs on your local machine or a cloud CI server.
Setting Up Headless Chromium for Export
To automate document generation in a Node environment, you need a headless browser controller. Puppeteer remains the standard tool for this job. Let's look at how to configure a script that reads your built pages and exports them.
First, install Puppeteer in your project dependencies.
npm install puppeteer
Next, create a generation script in your root directory, perhaps named generate-pdfs.js. You don't want to run this manually every single time. Hook it into your package.json build scripts so it triggers right after gatsby build.
Here's a basic script outline to get started:
- Launch a headless browser instance using
puppeteer.launch(). - Open a new page and navigate to your local or hosted static HTML file.
- Inject specific print styles or wait for network idle states so fonts and images load completely.
- Call
page.pdf()with your desired formatting options, such as format, margins, and background printing. - Close the browser instance.
If you're running this inside a Docker container on a continuous integration server, you might run into missing system dependencies. Make sure your CI environment includes the necessary Linux libraries for Chromium, or use a pre-built base image.
Crafting Print-Ready CSS Layouts
Screen design and print design have different rules. Screens use pixels, responsive grids, and endless scrolling. Print uses fixed dimensions like A4 or Letter, strict page budgets, and physical margins. If your Gatsby site uses CSS frameworks like Tailwind, you'll need custom styles to override screen defaults.
Create a dedicated print stylesheet and import it conditionally using media queries.
@media print {
body {
font-size: 12pt;
color: #000000;
background: #ffffff;
}
.no-print {
display: none !important;
}
.page-break {
page-break-before: always;
}
}
Controlling where pages split prevents awkward orphan headings and cut-off images. Use page-break-inside: avoid on cards, tables, and code blocks. If a code block spans across a page boundary, it breaks mid-line, which ruins readability. Forcing a clean break before major section headings keeps your document structured.
Images also need explicit sizing. If an SVG or PNG doesn't have explicit dimensions, the headless browser might render it at native resolution, blowing past your page margins. Set max-widths using percentages rather than fixed viewport units.
Handling Fonts and Assets
Missing fonts or broken image links are the most common reasons exports look unprofessional. When Puppeteer loads your page locally from the public folder, relative asset paths can sometimes fail if the base URL isn't configured correctly.
Always use absolute paths or configure the headless browser to intercept requests and map them directly to your local file system. If you're loading web fonts from external providers like Google Fonts, ensure the browser waits long enough for the font files to download before triggering the print command.
You can force Puppeteer to wait for specific network conditions:
await page.goto('http://localhost:9000/report', {
waitUntil: 'networkidle0',
});
Using networkidle0 tells the script to wait until there are no network connections for at least 500 milliseconds. This ensures all fonts, icons, and dynamic images render fully before the PDF engine captures the output.
Common Build Failures and Fixes
Building a reliable export pipeline takes iteration. You'll likely hit a few snags along the way. Let's look at common pitfalls and how to fix them.
Timeout errors happen when your Gatsby site takes too long to hydrate or fetch data during local preview. If you're running gatsby serve concurrently with your export script, make sure the server is fully ready before Puppeteer attempts to connect. Use a utility package to poll the port until it responds.
Memory leaks can crash CI pipelines if you generate hundreds of PDFs in a loop. Never open a new browser instance for every single page. Instead, launch one browser instance, open new pages within that same instance, close each page after export, and close the browser only when the entire batch finishes.
Font rendering glitches occur when local system fonts differ from web fonts. Embed your critical font files directly into your project's CSS using base64 encoding or local WOFF2 files rather than relying on external CDNs during the build step.
Frequently Asked Questions
Can I generate PDFs directly inside Gatsby components?
No. Gatsby builds static files on the server during compile time, but component code runs in Node without access to browser layout engines. You need an external Node script using a headless browser to capture the rendered output.
How do I add running headers and footers with page numbers?
Puppeteer has built-in options for headers and footers within the page.pdf() method. You can pass HTML strings containing specific CSS classes like pageNumber and totalPages to dynamically render pagination.
Why are my background colors missing in the output?
Headless browsers omit background graphics by default to save printer ink. You must explicitly pass the printBackground: true flag inside your Puppeteer PDF configuration object.
Is client-side JavaScript execution supported during export?
Yes, but you must configure your script to wait for hydration to complete. If your Gatsby site relies heavily on client-side state before rendering content, standard static HTML exports will be blank until you wait for the proper DOM selectors to appear.