Fix Slow HTML to PDF Conversion in Puppeteer
Find whether Puppeteer is slow during launch, navigation, readiness waits, or PDF rendering, then tune the measured bottleneck without sacrificing output quality.
To fix slow HTML-to-PDF conversion in Puppeteer, first time browser launch, navigation, explicit readiness waits, and page.pdf() separately. The delay may be navigation or runtime CPU throttling rather than PDF rendering. Then inspect font readiness, print CSS, and PDF options, changing one measured factor at a time. Puppeteer’s documentation describes defaults and behavior; it does not promise that any one option will speed up every document.
1. Measure each stage separately
Use the same representative page and record durations for browser startup, navigation, any application-specific wait, and PDF generation. Keep the output path and options consistent between runs. This gives you a useful first split: if navigation takes most of the time, changing PDF options is unlikely to address the bottleneck.
const { performance } = require('node:perf_hooks');
const puppeteer = require('puppeteer');
(async () => {
const mark = (label, start) => {
console.log(`${label}: ${(performance.now() - start).toFixed(0)} ms`);
};
let start = performance.now();
const browser = await puppeteer.launch({ headless: true });
mark('Browser launch', start);
try {
const page = await browser.newPage();
start = performance.now();
await page.goto('https://example.com', { waitUntil: 'load', timeout: 30000 });
mark('Navigation (load)', start);
// Add and time only readiness conditions your page actually requires.
start = performance.now();
await page.evaluate(() => document.fonts.ready);
mark('Font readiness (explicit measurement)', start);
start = performance.now();
await page.pdf({ path: 'output.pdf', format: 'A4', printBackground: true });
mark('PDF generation', start);
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Save as pdf.js, install Puppeteer with npm install puppeteer, then run node pdf.js. Replace the example URL with a page you are authorized to capture. The explicit font measurement is diagnostic; page.pdf() already waits for fonts by default. Avoid counting the same font wait twice when interpreting timings.
Read navigation timings correctly
page.goto() defaults to waiting for the load event. If you specify multiple waitUntil lifecycle events, navigation completes only after all specified events have fired. Measure navigation and any later selector, delay, or application-ready wait separately from PDF generation. Choose the least restrictive condition that still produces a complete document; do not remove readiness checks that protect output correctness just to reduce a timing number. [Puppeteer Page.goto API]
2. Check font readiness and print rendering
Puppeteer renders PDFs using the print CSS media type, so the page can lay out differently from its screen view. The PDF operation waits for fonts by default. Missing, late-loading, or numerous fonts can be useful areas to investigate, but Puppeteer’s documentation does not quantify their cost or say they are always the cause of a slow conversion. [Puppeteer Page.pdf API]
- Inspect the page using print media behavior and check whether print-specific styles trigger expensive layout or hide content unexpectedly.
- Confirm the intended fonts are available and load successfully. If the PDF needs particular glyph coverage, preserve font readiness rather than disabling it blindly.
- For Chinese, Japanese, or Korean output, Puppeteer’s troubleshooting guide notes that additional fonts may be required. Missing glyphs are a rendering issue, even if they are not the source of the delay. [Puppeteer troubleshooting guide]
- Compare a normal document and a representative complex document. A result from one short page does not establish how long a long or image-heavy document should take.
3. Review PDF options against a requirement
Check the options your code passes and the options it leaves at their defaults. The documented PDFOptions include a 30,000 ms default timeout, waitForFonts: true, and printBackground: false. The timeout is a failure limit, not an estimate of normal conversion time. printBackground controls whether background graphics are printed; changing it may change the result, and the docs make no universal speed claim for turning it off. [Puppeteer PDFOptions]
await page.pdf({
path: 'output.pdf',
format: 'A4',
printBackground: true,
waitForFonts: true,
timeout: 30000
});
This example makes several choices explicit; use the values your output requires. If generation legitimately takes longer than the configured timeout, raise the timeout only after measuring and checking that the page is not stuck. Increasing it gives a slow or hung operation longer to run; it does not make conversion faster.
4. Check the runtime and hosting environment
After measuring the stages, check where the browser runs. Container limits, CPU allocation, browser compatibility, or a suspended background task can dominate elapsed time. Puppeteer documents a Cloud Run case where CPU is disabled after an HTTP response is sent, making Puppeteer work started in the background appear very slow; for that background-work pattern, its guide says to enable CPU always. Apply this lead only if your deployment has that response-and-background-work shape. [Puppeteer troubleshooting guide]
- Compare timings in the same deployment context as the slow job; local measurements do not reveal a production CPU suspension policy.
- Check browser and Puppeteer versions alongside the deployment image. The troubleshooting guide flags Alpine compatibility and a reported timeout issue for a particular Alpine/Chromium combination; treat these as conditional compatibility leads, not a diagnosis for every Alpine workload.
- When behavior is unclear, capture page console messages and inspect the browser outside headless mode as Puppeteer’s troubleshooting guide describes. Browser-visible errors can reveal failed resources or page behavior that a PDF timeout alone cannot explain. [Puppeteer troubleshooting guide]
5. Re-measure one change at a time
- Record the four stage durations for a repeatable document.
- Identify the slowest stage and form a specific hypothesis: navigation wait, font readiness, print layout, browser startup, or runtime CPU availability.
- Change one relevant condition while keeping the document and output requirements fixed.
- Compare multiple representative runs and inspect the resulting PDF for font, layout, background, and content differences.
- Keep the change only if it improves the stage you intended without breaking the document’s requirements.
No source cited here provides a benchmark or expected percentage improvement for an unspecified document. A measured result from your workload is more useful than assuming a particular option is a speed fix.
6. Troubleshooting common slow-PDF symptoms
| Symptom | Likely area to inspect | Next step |
|---|---|---|
page.pdf() appears slow, but its own timer is short |
Browser launch, navigation, or explicit waits are being included in the overall duration. | Time each stage independently and inspect every waitUntil event and later readiness wait. |
| Navigation never seems to finish | The chosen lifecycle condition may wait on resources or events the workflow does not need. | Review the configured waitUntil list. Select an appropriate readiness condition for the page, then verify the PDF still contains required content. |
| PDF generation reaches a timeout | The operation exceeded its configured limit, or work is stalled by the page or environment. | Measure the PDF stage, inspect page and console behavior, and check runtime CPU availability. Raise the limit only when the longer duration is expected and acceptable. |
| Production is much slower than local runs | Deployment CPU allocation, container constraints, or browser/runtime compatibility may differ. | Compare the actual runtime settings and versions. For Cloud Run background work after an HTTP response, check the documented CPU-always guidance. |
| Fonts or glyphs are missing in the PDF | Fonts may not be ready or available in the runtime; some scripts need additional fonts installed. | Preserve font waiting when fidelity requires it, verify font loading, and check the runtime’s installed font coverage. |
| PDF appearance differs from the browser screenshot | page.pdf() uses print media CSS; print styles or background settings can alter output. |
Inspect print CSS and set options such as printBackground to match the intended document. |
7. Performance, reliability, and cost considerations
For performance, optimize the stage that measurements identify. Reusing a browser process may avoid repeated launch work in some architectures, but it does not remove navigation, font, print-layout, or CPU delays; evaluate it with your own workload. Keep an eye on memory and job concurrency when changing process lifetime, since a shared browser also changes how jobs share runtime resources.
For reliability, use a representative page, make readiness conditions explicit, retain font waits when they matter, and validate generated output after changes. Treat a timeout as a guardrail, not a performance control. If a job is handed off to background work, confirm the host continues to allocate CPU for that work.
For cost, Puppeteer itself does not define your infrastructure bill. Runtime duration, CPU and memory allocation, concurrency, and retries can affect hosting cost according to your provider’s pricing. The cited sources do not provide a cost estimate for this workflow; use your measured job duration and provider rates. Avoid retrying a page indefinitely when it is stuck.
8. Or skip the browser setup
If your goal is to capture a URL as a PDF without managing Puppeteer, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return a PDF. See the ScreenshotNeo API documentation for the 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
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "format": "pdf"},
timeout=90,
)
r.raise_for_status()
open("page.pdf", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
format: 'pdf'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('page.pdf', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month with no card.
FAQ
Does raising the PDF timeout make conversion faster?
No. It changes how long Puppeteer waits before timing out. Measure the PDF stage and diagnose why it is slow before deciding whether a longer limit is appropriate.
Should I set waitForFonts to false?
Only if your output requirements allow PDF generation without waiting for fonts. The default is true, and disabling it can affect font fidelity; the documentation does not claim a universal speed gain.
Why does the PDF not match the page on screen?
Puppeteer uses print CSS media for PDF generation. Check print-specific styles and relevant PDF options, including the default-off background printing behavior.
Can the documentation tell me how much faster my job will be?
No. The cited documentation describes API behavior, defaults, and certain runtime issues, but gives no general benchmark for an arbitrary page. Measure your own representative document.


