ScreenshotNeo

BlogHow-to

How to Capture a Website Page with Web Fonts Loaded in Puppeteer

Wait for the page’s content and `document.fonts.ready` before calling `page.screenshot()`. Here’s a runnable Puppeteer example, options, and fixes for common font-loading issues.

By the ScreenshotNeo team4 October 20266 min read

To capture a Puppeteer screenshot after web fonts have loaded, navigate to the page, wait for any page-specific content that affects its layout, await document.fonts.ready, then call page.screenshot(). Puppeteer’s screenshot options do not include a waitForFonts option. The similarly named PDF option applies to page.pdf(), not screenshots.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });

  // If needed, wait for content that changes the page before checking fonts.
  // await page.waitForSelector('[data-ready="true"]');

  await page.evaluate(() => document.fonts.ready);
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

networkidle2 is a navigation condition, not a guarantee that the fonts your screenshot needs are ready. Awaiting the browser’s font readiness promise makes that requirement explicit.

1. Install Puppeteer and run the capture

In a new project, install Puppeteer and save the example as an ES module:

npm install puppeteer

For example, use a .mjs file such as capture.mjs, then run node capture.mjs. Puppeteer launches a browser, opens a page, navigates, waits for fonts, writes a PNG, and closes the browser even if capture fails.

Replace https://example.com with the target URL. Add a page-specific wait only when the site renders relevant content after initial navigation.

2. Wait in the right order

  1. Navigate. Choose an appropriate waitUntil condition. networkidle2 is a common starting point, but a busy page may never become idle, and an idle network does not itself establish font readiness.
  2. Wait for the content that affects the capture. If client-side code inserts the page content or changes its styles late, wait for a selector or another application-specific signal before checking fonts.
  3. Wait for font readiness. Run await page.evaluate(() => document.fonts.ready). This evaluates the page’s FontFaceSet promise in the browser context.
  4. Capture. Call page.screenshot() with the desired capture scope and output settings.

If your application changes its content or font usage after the first font wait, wait for that change and await document.fonts.ready again before capturing. This is a workflow precaution for late changes; individual sites can have their own loading behavior.

3. Choose screenshot scope and output options

Page.screenshot() returns screenshot bytes and can also write them to a path. The screenshot options include capture scope and image settings. Check the API reference for the exact options supported by the Puppeteer version in your project.

Need Example What it does
Save the visible viewport as PNG await page.screenshot({ path: 'page.png' }) Captures the current viewport.
Capture the full page await page.screenshot({ path: 'page.png', fullPage: true }) Captures beyond the viewport to include the full page.
Choose an image format await page.screenshot({ path: 'page.webp', type: 'webp' }) Sets the screenshot type where supported. Common documented types include PNG, JPEG, and WebP.
Capture a region await page.screenshot({ clip: { x: 0, y: 0, width: 800, height: 600 } }) Captures the specified rectangle. Set dimensions appropriate to the page and desired output.
Capture one element const el = await page.$('main'); await el.screenshot({ path: 'main.png' }) Captures an element; Puppeteer attempts to scroll a hidden element into view.

When combining font readiness with an element screenshot, keep the font wait before the element’s screenshot call. For full-page captures, ensure the page’s relevant content has been rendered before taking the image.

4. Screenshot versus PDF font waiting

Puppeteer’s Page.pdf() behavior is separate from its screenshot API. The PDF guide says PDF generation waits for fonts by default, and the PDF options document waitForFonts as a PDF-specific option that defaults to true and waits for document.fonts.ready. That does not make it a screenshot option.

Output Method Font readiness
Image screenshot page.screenshot() Explicitly await document.fonts.ready before capture.
PDF page.pdf() waitForFonts defaults to true in the documented PDF options.

Do not pass waitForFonts to page.screenshot() expecting Puppeteer to delay the image capture. For PDF output, consult the PDF options for paper size, margins, orientation, page ranges, and other PDF-specific settings.

5. Troubleshooting web fonts in screenshots

Symptom Likely cause What to try
The screenshot uses a fallback font The capture ran before the page’s font readiness promise settled, or the page changed font usage afterward. Await document.fonts.ready after navigation and after any late content or style change that affects fonts.
The wait completes, but the intended font is absent The font may fail to load, be blocked, or not be used by the rendered content. Inspect the page’s network and console errors, font URLs, cross-origin configuration, and CSS font-family rules. Readiness does not make an unavailable font load successfully.
networkidle2 hangs or times out The site may keep network connections open or continue making requests. Choose a navigation condition that fits the site, then wait for a page-specific readiness signal and the font promise. Do not treat network idleness as the font check.
The screenshot has missing or shifted content Client-side rendering, images, or styles may still be changing after navigation. Wait for a selector or application signal for the content you need, then await fonts and capture. If the page changes typography afterward, repeat the font wait.
waitForFonts is rejected or has no effect The option was passed to page.screenshot(), where it is not documented. For screenshots, use page.evaluate(() => document.fonts.ready). Keep waitForFonts for page.pdf().
Element capture fails or omits the intended area The selector did not match, or the target element is not available in the expected state. Wait for the selector, verify it exists, and then call its screenshot method. Puppeteer attempts to scroll a hidden matched element into view.

6. Reliability, performance, and cost

  • Reliability: Use an explicit page-specific condition when a site renders asynchronously. Font readiness addresses font loading; it does not guarantee that unrelated content, images, or application work has finished.
  • Performance: Waiting for network idleness and font readiness can add capture time. Avoid arbitrary long delays when a meaningful selector or app readiness condition is available. A page that continually changes can require a site-specific strategy.
  • Failure handling: Keep browser cleanup in a finally block, as in the example. Set navigation timeouts deliberately for your workload and report failures rather than saving an image as if it were complete.
  • Cost: Running Puppeteer yourself has no per-screenshot API charge, but you are responsible for the machine, browser execution, maintenance, and handling failures. A hosted screenshot API trades that setup for its service pricing and limits.

7. Or skip the browser setup

If you need a screenshot without managing Puppeteer and a browser, ScreenshotNeo is a website screenshot API and MCP server. Its one-call API returns an image or PDF; see the ScreenshotNeo API documentation for request options. For a target page, use the API’s capture controls to set any needed wait condition and output format.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup 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 for AI agents. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

8. FAQ

Does document.fonts.ready guarantee every font file loaded successfully?

No. It is the page’s font readiness promise, but a font can be unavailable or fail to load. Check the browser’s network and console output when the intended typeface is missing.

Can I use this with a page that injects fonts after navigation?

Yes. Wait for the application’s content or state change first, then await document.fonts.ready immediately before capture. Repeat the wait if later work changes which fonts the page uses.

Should I use page.pdf() when I need a PNG?

No. Use page.screenshot() for an image and explicitly wait for fonts. Use page.pdf() when the desired output is a PDF.

References