Free tools Windows power users keep installed
One-click scans. No signup required.
If a custom font appears in a Puppeteer screenshot but not in its PDF, the usual explanation is that you are comparing two different rendering modes. page.screenshot() captures the page as displayed on screen, while page.pdf() uses the print CSS media type. Print rules may select another family or weight, hide the text, or expose a font request that fails in the PDF runtime.
Start by checking print CSS and the font request, then verify the actual computed style and font availability. Puppeteer already waits for document.fonts.ready by default; adding a wait alone cannot repair a failed request or an incorrect print rule.
Why the screenshot and PDF disagree
A screenshot and a PDF are not necessarily produced from the same CSS state. Puppeteer documents Page.pdf() as generating output with the print CSS media type. Consequently, rules inside @media print, print-specific selectors, and different font declarations can change the result even when the screen rendering is correct.
The PDF option waitForFonts defaults to true. It waits for document.fonts.ready, but that promise means the browser has finished its font-loading process; it does not prove that your intended family was requested successfully, that the requested weight exists, or that print CSS selected it.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →The three causes to separate
- Media mismatch: print CSS replaces the family, weight, style, or target content.
- Availability failure: the PDF process cannot fetch the remote font, cannot read a local file, or receives an invalid response.
- Timing or visibility: the page is backgrounded, the font is still being used by a late-rendered element, or the content is not present when the PDF is generated.
Do not assume an operating-system package is the fix. The correct remedy depends on the page’s CSS, font delivery method, runtime container, and Puppeteer/Chromium versions.
1. Reproduce both outputs from one page instance
Use the same URL, browser context, cookies, viewport, and page instance for the screenshot and PDF. Record your Puppeteer and Chromium versions. This removes differences caused by authentication, responsive breakpoints, or a second navigation.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.setViewport({width: 1440, height: 900, deviceScaleFactor: 1});
await page.goto('https://example.com/invoice', {waitUntil: 'networkidle0'});
await page.screenshot({path: 'screen.png', fullPage: true});
await page.pdf({path: 'document.pdf', format: 'A4', waitForFonts: true, printBackground: true});
await browser.close();
})();
If the screenshot is taken before navigation or font loading has completed, you may be comparing a partially loaded screen with a later PDF. Keep the navigation and diagnostics in the same execution path.
2. Inspect print CSS before changing code
Search your stylesheets for @media print, print-only classes, and declarations of font-family, font-weight, font-style, and font-display. Also check whether print selectors hide the element or replace it with generated content.
To inspect the state used by the PDF, switch the page to print media and read the target element’s computed style:
await page.emulateMediaType('print');
const printStyle = await page.$eval('.invoice-title', element => {
const style = getComputedStyle(element);
return {
text: element.textContent,
display: style.display,
visibility: style.visibility,
fontFamily: style.fontFamily,
fontWeight: style.fontWeight,
fontStyle: style.fontStyle,
fontSize: style.fontSize
};
});
console.log(printStyle);
If the family is different from the screen version, fix the print rule or deliberately choose the screen media type for PDF output. If the element is hidden, a font change is not the underlying problem.
Rank #2
Choose the intended media mode
| Choice | Use it when | Trade-off |
|---|---|---|
| Keep print media (default) | The PDF is a printable document and print layout should apply. | You must preserve the required family and weight in print rules. |
emulateMediaType('screen') |
The PDF should reproduce the on-screen design. | Screen CSS may have unsuitable page breaks, colors, margins, or pagination. |
For a screen-style PDF, call this before page.pdf():
await page.emulateMediaType('screen');
await page.pdf({path: 'screen-style.pdf', waitForFonts: true});
This changes the document’s media mode; it does not make a missing font available.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errors3. Make font readiness explicit
Although Puppeteer’s default is already to wait for fonts, making the intent visible helps during troubleshooting. If the page is in the background, bring it to the front first; the PDF options documentation notes that this may be necessary for the fonts-ready wait to resolve.
await page.bringToFront();
await page.pdf({
path: 'output.pdf',
waitForFonts: true,
printBackground: true
});
You can also await the browser-side promise before collecting diagnostics:
await page.evaluate(() => document.fonts.ready);
Neither step validates visual identity. Continue with the FontFaceSet, network, and computed-style checks below.
4. Verify that the intended face is usable
Run this diagnostic with the actual family, weight, and sample text used by the PDF. Execute it after selecting the relevant media type and after the target element exists.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #3
const result = await page.evaluate(async () => {
await document.fonts.ready;
const shorthand = '400 16px "Example Font"';
const target = document.querySelector('.invoice-title');
const style = target ? getComputedStyle(target) : null;
return {
status: document.fonts.status,
expectedFontAvailable: document.fonts.check(shorthand, 'Sample text'),
family: style?.fontFamily ?? null,
weight: style?.fontWeight ?? null,
style: style?.fontStyle ?? null
};
});
console.log(result);
document.fonts.check() returning true is useful evidence, not proof that the PDF uses the same face. Interpret it with the computed family and weight, the actual text element, and the network response for the font file.
Inspect requests and browser errors
Capture failed requests and console messages while loading the page:
page.on('requestfailed', request => {
const type = request.resourceType();
if (type === 'font') {
console.error('Font request failed:', request.url(), request.failure());
}
});
page.on('response', response => {
if (response.request().resourceType() === 'font') {
console.log('Font response:', response.status(), response.url());
}
});
page.on('console', message => {
if (message.type() === 'error') console.error('Browser error:', message.text());
});
A failed request, an unexpected status, a blocked cross-origin response, or an HTML error page returned with a font URL points to delivery or deployment rather than Puppeteer timing. Check that the PDF process can reach the URL and that the response contains the intended font file.
5. Check CSS names, weights, and styles
The family name in font-family must match the family declared by @font-face. A file named “Bold” does not automatically satisfy a request for weight 700 unless the face is declared with that weight. Likewise, an italic request needs an italic face or a deliberate fallback.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match@font-face {
font-family: 'Example Font';
src: url('/fonts/example-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Example Font';
src: url('/fonts/example-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
.invoice-title {
font-family: 'Example Font', sans-serif;
font-weight: 700;
}
Inspect the computed font-weight under print media. If print CSS requests 600 but only 400 and 700 faces are supplied, the browser may synthesize or fall back. Make the print declaration correspond to a supplied face when exact output matters.
6. Check deployment access only after the evidence points there
Remote fonts
- Confirm the URL resolves from the machine or container running Chromium.
- Check response status and content type, redirects, authentication, and cross-origin policy.
- Ensure a proxy, firewall, or content-security policy is not blocking the request.
Local fonts and containers
- Verify that the referenced file is present in the runtime image, not only on the development machine.
- Check path casing and permissions.
- Confirm that the CSS family name matches the declared face; installing an operating-system font will not correct a wrong CSS name.
The available evidence does not identify a universal package or platform command. Apply a deployment-specific change only after the request and CSS checks show that deployment is the cause.
7. Generate the PDF with a controlled sequence
This complete example combines navigation, print inspection, an explicit readiness wait, and PDF generation:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({headless: true});
try {
const page = await browser.newPage();
page.on('requestfailed', request => {
if (request.resourceType() === 'font') {
console.error('Font failed:', request.url(), request.failure());
}
});
await page.goto('https://example.com/invoice', {
waitUntil: 'networkidle0',
timeout: 60000
});
await page.emulateMediaType('print');
await page.bringToFront();
await page.waitForSelector('.invoice-title', {visible: true});
await page.evaluate(() => document.fonts.ready);
const check = await page.evaluate(() => {
const element = document.querySelector('.invoice-title');
const style = getComputedStyle(element);
return {
status: document.fonts.status,
family: style.fontFamily,
weight: style.fontWeight,
available: document.fonts.check('700 16px "Example Font"', element.textContent)
};
});
console.log(check);
await page.pdf({
path: 'invoice.pdf',
format: 'A4',
printBackground: true,
waitForFonts: true,
margin: {top: '16mm', right: '16mm', bottom: '16mm', left: '16mm'}
});
} finally {
await browser.close();
}
})();
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.8. Troubleshooting by symptom
The PDF always uses a system fallback
Inspect the font request response and document.fonts.check(). A failed or blocked request indicates delivery; a successful request with a different computed family indicates CSS selection. Fix that specific layer rather than adding longer delays.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Only bold or italic text is wrong
Compare the requested weight/style with the declarations in every @font-face. Supply the exact face or change the print rule to a weight you actually deliver.
The screenshot is correct, but the PDF loses the font after adding @media print
Temporarily remove the print font override or set the intended family in the print rule. If the screen design is the requirement, use emulateMediaType('screen') and then review pagination and print colors.
Waiting for fonts hangs or never appears to finish
Call page.bringToFront(), confirm the target page is active, and inspect failed requests and console errors. A wait cannot complete usefully when the page is continuously attempting blocked resources.
The diagnostic says the font is available, but the PDF still looks different
Compare the exact element, text, weight, and media mode. Check for variable-font axes, generated content, fallback glyphs, and a second stylesheet loaded after your inspection. Preserve a minimal reproduction with browser and Puppeteer versions if the computed print style and successful request agree but output remains wrong.
Recommended Free Tools
Best Value
It worked locally but fails in CI
Compare the runtime container, network access, file paths, locale, browser revision, and authentication. Reproduce the failing environment rather than installing an unspecified font package based only on the symptom.
9. Performance, reliability, and maintenance
- Use
networkidle0selectively: applications with analytics or long-polling may never become idle. In that case, wait for the target element anddocument.fonts.readyinstead. - Keep a font manifest: record each family, weight, style, URL, and expected response so a deployment change is detectable.
- Test both media modes when both matter: a screen screenshot can pass while print CSS silently selects a fallback.
- Pin and record versions: include Puppeteer and Chromium versions in bug reports and CI artifacts.
- Save diagnostics with failures: retain computed styles, font request statuses, browser console errors, and the generated PDF.
Or skip the browser setup
If you need a clean website capture rather than a locally managed Puppeteer pipeline, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for all options, including custom CSS and JavaScript, waiting for a selector or network idle, device and viewport settings, full-page lazy-image loading, PDF output, request blocking, authentication headers, cookies, geolocation, caching, signed links, asynchronous jobs, webhooks, bulk capture, and usage reporting.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is included on every plan. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Sign up for the free ScreenshotNeo plan.
FAQ
Does setting waitForFonts: true force a particular font?
No. It waits for the browser’s font readiness promise; it does not select a family, repair CSS, or make an unreachable font URL work.
Should every PDF use screen media?
No. Use screen media only when reproducing the screen design is the requirement. Printable documents generally need print media and print-specific layout rules.
Is a historical custom-font issue proof that current Puppeteer is broken?
No. A historical report shows that developers have encountered the symptom, but it does not establish the cause or prove that the same issue remains in current releases.
Frequently Asked Questions
Can a screenshot prove that the font file loaded successfully?
No. A screenshot can look correct because of a fallback or because screen CSS selects a different face. Confirm the print computed style, FontFaceSet result, and font request response.
What information should accompany a Puppeteer bug report?
Include a minimal page, print CSS, the font response details, Puppeteer and Chromium versions, runtime environment, diagnostic output, and the generated PDF.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




