How to Save a Webpage as PDF with Puppeteer and Wait for Fonts to Load
Use Puppeteer’s built-in font wait to create a webpage PDF with the right typography, print styles, and backgrounds.
Use Puppeteer’s page.pdf() after navigating to the page. Set waitForFonts: true to wait for document.fonts.ready before PDF generation; it is already the documented default, but setting it explicitly makes the requirement clear. Puppeteer prints using print CSS by default, and printBackground is off unless enabled.
This guide covers font readiness, a complete runnable script, print and screen styling, paper and layout options, reliability, common failures, and alternatives. See the official PDFOptions reference and PDF generation guide.
Complete runnable example
Install Puppeteer in a Node.js project, save the following as save-page.mjs, then run node save-page.mjs https://example.com. Puppeteer downloads a compatible browser during installation unless configured otherwise.
npm install puppeteer
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
page.setDefaultNavigationTimeout(60_000);
const response = await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60_000,
});
// goto can return a response with an HTTP error status. Check it explicitly.
if (response && !response.ok()) {
throw new Error(`Navigation failed: HTTP ${response.status()}`);
}
// Optional: Puppeteer notes that bringing a background page to the front
// may be needed for its font wait to complete.
await page.bringToFront();
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
waitForFonts: true,
});
} finally {
await browser.close();
}
The essential sequence is page.goto(), followed by page.pdf(). waitUntil: 'networkidle2' is a navigation condition used in Puppeteer’s PDF guide; it is not the font wait. The PDF option independently waits for document.fonts.ready. See PDFOptions and Page.
What the font wait guarantees
waitForFonts defaults to true in the documented PDFOptions API. It waits for the page’s FontFaceSet readiness promise, document.fonts.ready, before generating the PDF. Specify it explicitly when the font behavior is important to the output or when making the script self-documenting.
This waits for font loading to reach the browser’s ready state. It does not make an inaccessible font URL load successfully, fix a malformed font declaration, or guarantee that the intended family is available. If the site falls back to another font, inspect the page’s font requests and CSS as well as the PDF.
Font readiness versus network idle
These conditions answer different questions:
| Mechanism | What it waits for | When to use it |
|---|---|---|
waitForFonts: true |
The document font set’s readiness before PDF generation | Use for the PDF’s font-specific readiness condition |
waitUntil: 'networkidle2' |
A navigation lifecycle condition based on network activity | Use when the page should settle after navigation, where suitable |
page.waitForNetworkIdle() |
Network idleness for at least its configured idle time | Use when application behavior requires a separate idle wait |
A page can be network-idle while its application has not reached the state you want to print. Conversely, pages with analytics, polling, or streaming requests may not become idle promptly. Use the font option for fonts and an application-specific readiness condition for application content. See Puppeteer’s waitForNetworkIdle reference.
Print CSS, screen CSS, and colors
page.pdf() uses print CSS media by default. This means print-specific rules such as @media print and print layout behavior can change the result from what you see in a normal browser tab.
To generate a PDF using screen media styling, emulate it before calling pdf():
await page.emulateMediaType('screen');
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
waitForFonts: true,
});
For print output, use page.emulateMediaType('print') if you want to state the default explicitly. Puppeteer notes that PDF output modifies colors for printing by default. To request more exact CSS colors, set -webkit-print-color-adjust in the page’s print styles, for example:
@media print {
html {
-webkit-print-color-adjust: exact;
}
}
printBackground controls whether CSS backgrounds are printed; its default is false. Turn it on when backgrounds, colored sections, or background images are part of the intended document. It does not replace color adjustment rules. See the Page reference and PDFOptions.
PDF options to choose deliberately
The full PDFOptions reference documents paper sizing, margins, orientation, ranges, output path, scale, timeout, and other settings. These are the choices most likely to affect a webpage-to-PDF workflow:
| Option | Use | Important detail |
|---|---|---|
path |
Write the PDF to a file | Omit it when using the returned PDF bytes in supported workflows. |
format |
Choose a standard paper format such as 'A4' or 'Letter' |
The documented default is Letter. |
width, height |
Set paper dimensions directly | Use instead of a standard format when the output requires custom dimensions. |
landscape |
Use landscape page orientation | Set true for wide tables or layouts. |
margin |
Set paper margins | Use top, right, bottom, and left values to control printable space. |
printBackground |
Include CSS backgrounds | Defaults to false. |
preferCSSPageSize |
Honor CSS @page size declarations |
Useful when the page’s print stylesheet owns the paper dimensions. |
pageRanges |
Print selected pages | Useful for extracting a subset from a long document. |
scale |
Scale page rendering | Use cautiously: scaling affects text size and layout fit. |
waitForFonts |
Wait for font readiness | Defaults to true in the documented API. |
timeout |
Set PDF generation timeout | The documented default is 30,000 ms; page default timeout settings can also be configured. |
For example, explicit margins and landscape orientation can be combined with font waiting:
await page.pdf({
path: 'report.pdf',
format: 'A4',
landscape: true,
margin: {
top: '12mm',
right: '10mm',
bottom: '12mm',
left: '10mm',
},
printBackground: true,
waitForFonts: true,
timeout: 60_000,
});
Application-specific readiness
The built-in font wait addresses font readiness. If your page fills in data after navigation, waits for an interactive chart, or reveals content after an application event, wait for that condition separately. Puppeteer’s Page.waitForFunction() can wait until a page-context expression becomes truthy, including an asynchronous condition.
// Example: wait for an application-owned marker that indicates content is ready.
await page.waitForFunction(
() => document.querySelector('[data-report-ready="true"]') !== null,
{ timeout: 15_000 },
);
await page.pdf({
path: 'report.pdf',
waitForFonts: true,
printBackground: true,
});
Replace the selector with a condition your application actually sets. Avoid arbitrary delays when a reliable page condition is available: a fixed delay may be too short on a slow run and unnecessarily long on a fast one.
Reliability and performance notes
- Use bounded waits. Set navigation and application readiness timeouts appropriate to the workload. PDFOptions documents a 30-second default PDF timeout, and Puppeteer allows page default timeout settings.
- Check navigation status.
page.goto()can return a response for HTTP statuses such as 404 or 500 without throwing. Checkresponse.ok()when an error page should not be saved. The response may benullin some cases. See the goto reference. - Do not treat idle as universal. Sites with long-lived network activity may make a network-idle condition unsuitable. Prefer the narrowest readiness condition that matches the page.
- Keep browser cleanup in
finally. Closing the browser even when navigation or PDF generation throws avoids leaving browser processes behind. - Expect rendering work. Large, long, image-heavy pages take more time and memory to lay out and print. Limit page ranges where appropriate, and avoid generating duplicate PDFs when the content has not changed.
- Keep output settings stable. Fix the browser version, viewport, media type, fonts, paper size, and margins for repeatable output across runs.
Puppeteer’s documentation establishes the API defaults and behavior but does not publish a general PDF-generation benchmark or per-run cost figure. Resource usage depends on the page, browser environment, and output size; measure the workload in the environment where it will run rather than relying on a universal estimate.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The PDF uses a fallback font | The font URL failed, the CSS family name does not match, or the intended face is unavailable. | Inspect font requests and computed styles; confirm the page can access the font. Keep waitForFonts: true, which waits for readiness but cannot repair a failed font load. |
| Font waiting does not finish in a background page | Puppeteer notes font waiting may require the page to be active. | Call await page.bringToFront() before generating the PDF. |
| Navigation times out on a page that keeps making requests | The selected network-idle condition may not occur because of polling or persistent traffic. | Use a suitable navigation lifecycle condition, then wait for a specific application-ready signal. Do not use network idle as a proxy for fonts. |
| PDF generation times out | The page or output is expensive to render, or the timeout is too short for the workload. | Inspect page complexity and readiness waits; set a larger PDF timeout or page default timeout when justified. |
| Colors or background sections are missing | Background printing is disabled, or print color adjustment changes CSS colors. | Set printBackground: true and use -webkit-print-color-adjust: exact in print CSS when exact colors are required. |
| The PDF layout differs from the browser view | page.pdf() uses print media by default, and print CSS may alter layout. |
Use print styles intentionally, or call page.emulateMediaType('screen') before PDF generation for screen styling. |
| A 404 or 500 page is saved without a navigation exception | Valid HTTP error statuses do not necessarily cause page.goto() to throw. |
Inspect the returned response and reject non-OK statuses when appropriate. Account for cases where the response is null. |
| PDF navigation behaves differently in headless shell | Puppeteer documents that headless shell does not support PDF navigation. | Generate the document through page.pdf() in a runtime that supports it; do not rely on navigating directly to a PDF in headless shell. |
Or skip the browser setup
If your goal is to capture a page as an image or PDF without managing a browser, [ScreenshotNeo](https://screenshotneo.com) provides a website screenshot API and MCP server. Its API can return a PDF with one GET request. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-d format=pdf \
-o page.pdf
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server lets AI agents use screenshot tools, including take_screenshot and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Do I need to call document.fonts.ready myself?
Usually no. page.pdf({ waitForFonts: true }) performs that font-readiness wait, and the documented default is already true.
Does waiting for fonts wait for every image and API request?
No. It waits for font readiness. Add separate readiness conditions for content your application loads independently.
Can I use a custom paper size?
Yes. PDFOptions supports width and height dimensions as well as standard formats. Use CSS @page sizing with preferCSSPageSize when the print stylesheet should control the page size.
Will Puppeteer always throw when the webpage returns an error status?
No. A 404 or 500 can still produce a navigation response. Check the response status yourself when those pages should not become PDFs.


