ScreenshotNeo

BlogHow-to

How to Wait for Fonts Before Taking a Website Screenshot with Puppeteer

Wait for the page’s used fonts and layout to settle before capturing with Puppeteer. Learn when to use document.fonts.ready, explicitly load a font, and troubleshoot fallback-font screenshots.

By the ScreenshotNeo team4 October 20267 min read

Before calling page.screenshot(), await document.fonts.ready in the page. This waits for the document’s used fonts and related layout work to finish. If the page renders content asynchronously, wait for its own readiness signal first.

await page.goto(url, { waitUntil: 'networkidle2' });
// Wait for a real application-ready condition here if the page needs one.
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'screenshot.png' });

This sequence makes font readiness explicit; it does not mean every font declared in CSS loaded successfully. See the Puppeteer screenshots guide, MDN’s FontFaceSet.ready reference, and the ScreenshotNeo API docs.

1. Wait for page content, then fonts, then capture

page.goto() resolves according to its navigation condition. That condition does not necessarily mean an application has finished rendering content. For single-page apps or pages that insert content after navigation, wait for a selector or other site-specific signal that genuinely indicates the content is ready. Then await the font set and take the screenshot.

const puppeteer = require('puppeteer');

async function capture(url, outputPath) {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2' });

    // Replace this with a readiness signal that exists on the target site.
    // Remove this line if the page has no separate application-ready signal.
    await page.waitForSelector('#app-ready');

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

capture('https://example.com', 'screenshot.png').catch(error => {
  console.error(error);
  process.exitCode = 1;
});

#app-ready is a placeholder, not a universal selector. Replace it with a real selector or site-specific condition, or omit the wait when navigation is enough. The order matters: if application rendering adds text that uses a font after the font wait, that later text may still trigger a font load and layout change.

2. Choose a navigation condition that fits the page

Puppeteer supports navigation wait conditions such as load, domcontentloaded, networkidle0, and networkidle2. Network idle can be a useful starting point, but persistent connections, polling, analytics, or late application work can make it unsuitable as the only readiness check. Pick a navigation condition for the site, then use a meaningful application signal where needed. The explicit font wait remains a separate step.

Need Approach
Simple page with no later rendering Navigate, await document.fonts.ready, capture.
App fills in content after navigation Navigate, wait for its actual ready selector or condition, await fonts, capture.
A specific font face is required Explicitly request it with document.fonts.load(), check the result, then capture.
Images or animations affect the intended output Wait for those separately; font readiness does not cover them.

3. Explicitly request a particular font when necessary

For ordinary page capture, document.fonts.ready is the page-wide signal for used-font loading and layout. If your code depends on a particular face being available, request it explicitly using a CSS font description. The promise returns matching loaded FontFace objects and rejects if the font request fails.

const faces = await page.evaluate(async () => {
  return await document.fonts.load('400 16px "Example Sans"', 'Representative text');
});

if (faces.length === 0) {
  throw new Error('The requested font did not match a loaded font face');
}

await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'screenshot.png' });

Replace Example Sans with the actual family and choose a weight, size, and representative text that match the content. A successful request does not prove every other declared face loaded; it addresses the font request you made. If the font is cross-origin, the font host must also serve it in a way the browser can use.

4. Set screenshot output options independently

Font readiness and screenshot geometry are separate concerns. Use screenshot options to choose the output shape after the page is ready. Common options include path for a saved file, fullPage for the full page, clip for a rectangular region, and type for an image format supported by Puppeteer.

// Viewport screenshot
await page.screenshot({ path: 'viewport.png' });

// Full-page screenshot
await page.screenshot({ path: 'full-page.png', fullPage: true });

// Capture a region in page coordinates
await page.screenshot({
  path: 'region.png',
  clip: { x: 0, y: 0, width: 800, height: 500 }
});

Set the viewport before navigation if the target layout depends on screen size. These options affect what is captured, not when fonts are ready. See Puppeteer’s ScreenshotOptions reference for the full option set.

5. Edge cases and limits

  • Late content: A font wait cannot anticipate text the application has not rendered yet. Wait for that content first, then wait for fonts.
  • Optional or unused faces: The used fonts can differ from all faces declared in stylesheets. Readiness is not proof that every declared font downloaded.
  • Font failure: A failed or optional font may leave fallback typography in place. Explicitly load a required face and handle rejection if its availability is essential.
  • Images and animations: Font readiness does not wait for image downloads, CSS transitions, video frames, or arbitrary asynchronous work. Handle those according to the capture requirement.
  • Fixed sleeps: A delay may hide timing issues on one run but does not establish that the app or font is ready. Prefer selectors and browser readiness promises.

6. Troubleshooting fallback-font screenshots

Symptom Likely cause Fix
Screenshot uses a system or fallback font Capture happened before the used font finished loading, or the font request failed. Await document.fonts.ready; inspect the font request in the browser context and explicitly call document.fonts.load() if a specific face is required.
The wait completes, but typography is still wrong The intended face may not be used, may not match the requested weight/style, or may have failed and fallen back. Check the computed font family and weight for the affected element; verify the matching face and resource are available.
App content appears after the screenshot Navigation completed before the app rendered its final content. Wait for a real app-ready selector or condition before awaiting fonts.
Navigation hangs at network idle Long-lived network activity prevents the chosen idle condition. Choose a suitable navigation condition and wait for a specific content-ready signal instead of treating global network idle as the only criterion.
Explicit font load rejects The requested face could not be loaded, such as because the family/descriptor does not match or the resource is unavailable. Check the CSS font descriptor, family spelling, weight, and font resource response; handle the rejection rather than silently capturing.
Capture is stable for text but not images or motion The font promise only concerns fonts and layout. Add separate waits for the relevant images, selector state, or animation state.

7. Performance, reliability, and cost

Awaiting document.fonts.ready adds only the time needed for the document’s used-font loading and associated layout to settle; the exact delay depends on the page and its font resources. A required explicit font request can add network work if that font was not already needed. Avoid waiting for every declared face unless your output depends on them.

For repeatable captures, use a stable application-ready condition and handle navigation, selector, and font-load failures as errors. Keep browser cleanup in a finally block, as in the runnable example, so failures do not leave the browser process open. With your own Puppeteer setup, account for the runtime and infrastructure you operate; there is no per-screenshot API charge from Puppeteer itself. A hosted screenshot API can trade browser setup and maintenance for a per-plan usage allowance.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Its one-call capture example is below; see the API documentation for options.

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}`);
  • Cookie and consent banners, newsletter popups, and chat widgets are handled before the capture; each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
  • An MCP server gives AI agents tools for screenshots, page information, and PDF capture.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

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

FAQ

Does Puppeteer automatically wait for fonts before a screenshot?

The cited Puppeteer screenshot documentation does not establish that it waits for font readiness. Await document.fonts.ready explicitly before capture.

Does document.fonts.ready guarantee every CSS font loaded?

No. It resolves for the document’s used fonts and associated layout work; declared but unused faces may not be part of that set.

Should I use document.fonts.load() or document.fonts.ready?

Use ready for page-wide used-font readiness. Use load() when you must explicitly request a particular face.

Will the font wait also wait for lazy images?

No. Add an image or page-specific readiness condition if those assets must be present in the screenshot.