How to Fix Fonts Not Loading in Website Screenshots
Find out why a screenshot uses fallback fonts, check whether the right font loaded, and fix timing, CSS, delivery, or browser issues.
If a website screenshot uses the wrong typeface, first determine whether the browser captured too early or whether the intended font failed to load or match the text. Wait for the page’s final content state, then wait for font readiness; if a specific face matters, explicitly request it and inspect the result. If no screenshot was produced and the capture log says it is waiting for fonts, investigate a capture wait or browser issue separately from incorrect typography.
1. Identify which font problem you have
There are two common symptoms, and they need different diagnosis:
| Symptom | Likely area to investigate |
|---|---|
| An image was produced, but the text looks different from the expected design | Font selection, face matching, glyph coverage, font delivery, or capture timing |
| No image was produced and the capture reports that it is waiting for fonts | A blocked or slow font request, application lifecycle, or automation/browser readiness issue |
A log that says it is waiting for fonts identifies the current wait stage; it does not, by itself, tell you which underlying cause applies. Playwright issue reports document particular timeout and readiness-hang cases, but they are examples rather than a general explanation for every similar failure.
2. Wait for the application state, then fonts
A page load event only tells you about a browser navigation milestone. It does not prove that the content you intend to capture has appeared or that its chosen font face has loaded. Wait for a meaningful page element or application state first, then check font readiness.
This runnable Playwright example uses Node.js. Install Playwright in your project and install its Chromium browser with npm install playwright and npx playwright install chromium. Save the example as capture.cjs and run it with node capture.cjs https://example.com.
const { chromium } = require('playwright');
(async () => {
const url = process.argv[2];
if (!url) throw new Error('Usage: node capture.cjs https://example.com');
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.locator('main').waitFor({ state: 'visible', timeout: 30000 });
const fontCheck = await page.evaluate(async () => {
await document.fonts.ready;
const spec = '400 16px "Example Sans"';
const sample = 'Screenshot sample text';
const faces = await document.fonts.load(spec, sample);
return {
status: document.fonts.status,
requestedFaceCount: faces.length,
checkPassed: document.fonts.check(spec, sample),
faces: faces.map(face => ({
family: face.family,
weight: face.weight,
style: face.style,
status: face.status
}))
};
});
console.log('Font readiness:', fontCheck);
await page.screenshot({ path: 'capture.png', fullPage: true });
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
Replace main, the family, weight, size, and sample text with the page’s actual capture requirements. If the screenshot targets a component, wait for that component and use sample text that exercises its characters. An empty result or a passing readiness check does not prove by itself that every visible element rendered in the intended face: inspect the computed styles and actual font request too.
What the font checks tell you
document.fonts.readyresolves when the document’s font loading and layout work has reached a settled state.document.fonts.load(fontSpec, sampleText)requests matching faces for the supplied font shorthand and text, and returns the faces it matched.document.fonts.check(fontSpec, sampleText)checks whether text can be rendered without pending loads for that request. It is useful as a readiness signal, but it does not certify that the browser selected the branded face rather than a fallback.FontFace.statuscan help identify whether a matched face is unloaded, loading, loaded, or errored.
The Font Loading API documents these methods and their limits. A face may also cover only part of a font’s character set, so test representative text, including non-Latin characters, symbols, or emoji when they appear in the capture.
3. Check the requested face against the CSS
When text looks wrong, inspect the affected element in browser developer tools and verify the full font match:
- Read the computed
font-family,font-weight, andfont-stylefor the exact text node. - Find the matching
@font-facedeclaration and confirm that its family, weight range, style, and source file cover that request. - Check whether the rendered text contains characters outside the file’s declared
unicode-rangeor actual glyph coverage. - Inspect the network panel for the font request. Check status, redirects, response content type, and console errors.
- If the browser reports a blocked request, investigate the specific cross-origin, content-security, or server response issue shown there.
For example, a page may declare a regular face but request a heavier weight. The browser can synthesize a weight or choose another face. A separate font subset may be needed for the text’s script. Do not assume a particular hosting, licensing, or network defect without evidence from the affected page.
The CSS font-display setting controls how text is shown while a face loads. Depending on its value and timing, a capture can encounter fallback text before a web font becomes available. Check the declaration and observe the actual rendering sequence; changing the display policy is not a substitute for fixing a failed request or waiting for the intended capture state.
4. Diagnose capture waits and browser-specific hangs
If the capture never completes at its font wait, first verify that ordinary navigation and the target content are working. Then compare the same page in a controlled run using the exact automation package, browser engine, and operating system used by the screenshot job.
Playwright discourages using networkidle as a general test readiness signal and recommends checking application state. A quiet network does not establish that a particular font loaded. Likewise, a screenshot assertion that retries until images match can help with visual stability but does not prove that the preferred typeface was selected.
A reported issue describes a hang in Playwright 1.63.0 with bundled WebKit 26.6 on Linux, with a reported successful control on 1.60.0. That issue is open and specific; it is not a general version rule. Reproduce with a minimal page, record the browser and automation versions, and compare engines before attributing a hang to the site or to a library defect.
Some screenshot tools provide a way to bypass or disable a font-ready wait. Treat that only as a diagnostic: it can produce an image with fallback typography and does not make the font load correctly. Use that output only when the fallback appearance is acceptable, such as a labeled failure artifact.
5. Keep visual comparisons consistent
For visual regression captures, keep the operating system, browser engine and version, browser settings, and available fonts consistent between baseline and comparison runs. Playwright documents that browser rendering can vary with the host operating system, version, settings, hardware, power source, headless mode, and other factors. Screenshot stability checks and environment consistency reduce noise; neither confirms that a specific web font loaded.
6. Troubleshooting checklist
| What you see | Possible cause | What to check or change |
|---|---|---|
| Fallback font in the image | Capture ran before the web font finished loading | Wait for the page’s final state, then await document.fonts.ready; explicitly load the needed face and sample text. |
| Fallback font remains after readiness | Wrong family, weight, or style match; missing glyphs; failed font response | Inspect computed styles, the matching @font-face, character coverage, and the font request’s network result. |
| Font request appears blocked | Browser rejected the request under a site or server policy | Use the console and network error to identify the applicable cross-origin, content-security, redirect, or response issue, then correct that specific configuration. |
| Capture times out while waiting for fonts | Slow or blocked request, application lifecycle issue, or automation/browser defect | Separate navigation from capture readiness, reproduce in the same versions, and test a minimal case. The wait log alone does not identify the root cause. |
| Text differs between developer machine and CI | Different platform, browser, settings, or installed fonts | Pin and match the visual test environment; verify web-font delivery independently. |
| Only some letters look wrong | Subset or glyph coverage mismatch | Test the actual character sample and inspect face declarations and Unicode ranges for the relevant script. |
| Network idle occurs but the font is still wrong | Network quiet was treated as proof of typography readiness | Check the face itself, its status, computed selection, and representative glyphs; do not rely on network idle alone. |
7. Performance, reliability, and cost
- Performance: Waiting for application state and a specific face avoids arbitrary long sleeps. Request only the needed face and representative text where practical; waiting for all fonts on a page can add delay when unrelated content loads extra faces.
- Reliability: Use bounded navigation and selector timeouts, log font status and failed requests, and capture the browser and operating system versions with visual artifacts. A timeout should report which stage failed so a font-delivery failure can be distinguished from a readiness hang.
- Cost: A self-hosted Playwright run uses your own browser infrastructure, so account for its compute and maintenance costs. A screenshot API substitutes a per-plan capture allowance for browser setup; compare its documented billing behavior and options against your workload.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It can return a screenshot in one request, with options for custom CSS and JavaScript, waits, viewport and device presets, full-page capture, and more. Its clean-shot flow accepts cookie or consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. AI agents can use its MCP tools to take screenshots, get page information, and capture PDFs.
One-call example, using the documented ScreenshotNeo API options:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed; an MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.
Frequently asked questions
Does document.fonts.ready guarantee the branded font is visible?
No. It is a useful readiness signal. Confirm the requested face, computed styles, network response, and glyph coverage for the text you capture.
Should I always wait for networkidle?
No. Network quiet is not proof that the needed face rendered, and Playwright discourages network idle as a general test readiness condition. Wait for the relevant application state and inspect font readiness.
Can I fix the screenshot by changing the font family?
Only if the page is meant to use a different family. First find out whether the intended face is simply late, mismatched, blocked, or missing glyphs.
Why do screenshots differ across operating systems?
Browser rendering and font availability can vary by platform and environment. Keep those conditions consistent for visual comparisons, then verify the web font independently.


