URL2PNG Screenshots Show Broken Fonts: Troubleshooting Guide
Find out whether missing or substituted fonts come from the page or URL2PNG capture timing, then troubleshoot font delivery, readiness, and cache.
A URL2PNG screenshot with missing, substituted, or visually incorrect fonts can have two broad causes: the page did not load the intended font, or the screenshot was captured before the browser finished loading it. First compare the same page in a normal browser. If its fonts are also wrong, investigate the page’s CSS and font requests. If the browser looks correct but the screenshot does not, test URL2PNG’s say_cheese readiness option or a measured delay, then force a fresh capture with unique if the image may be cached. A delay cannot repair an invalid or inaccessible font resource.
1. Separate a page font problem from a capture timing problem
- Open the exact target URL in a normal browser at roughly the same viewport used for the screenshot. Wait for the page to settle and check the family, weight, and glyphs.
- Compare the browser render and URL2PNG image. Keep the URL, viewport, user agent, and page state as similar as possible.
- Inspect the browser’s developer tools Network panel. Filter for font resources and check whether each expected request completes successfully and returns usable font data.
- Inspect the relevant
@font-facerules and computed styles. Confirm the requested family and weight exist, the URL is correct, and anyunicode-rangecovers the characters that look wrong. - If the browser and screenshot are both wrong, fix the page’s declarations, font hosting, or resource delivery first. If only URL2PNG is wrong, investigate readiness, cache, and rendering-environment differences.
Web-font loading is asynchronous. Browsers can show fallback text, or temporarily leave text invisible, while a font downloads. Browser behavior differs, so a screenshot taken during that interval can capture a transient state. See Google’s overview of font delivery and browser behavior and WebKit’s explanation of font loading and the CSS Font Loading API.
2. Check font requests and CSS
Verify the font resource
In developer tools, inspect the request URL, status, response, and initiator for the font file. Check for a missing file, an incorrect path, a failed request, or an unexpected redirect. If the font is hosted on another origin, review that origin’s access configuration as well. A screenshot service cannot use a font the page itself cannot fetch.
Verify family, weight, and character coverage
Compare the element’s computed font-family and font-weight with the available @font-face declarations. A page can successfully load one weight while requesting another that is not declared. For subset fonts, check whether the needed characters fall within the declared unicode-range; missing coverage can make only some characters fall back.
Google Fonts notes that its stylesheet response is tailored to the requesting user agent and that browsers can behave differently while the font loads. Match the capture’s user agent where possible before treating a browser-versus-image difference as proof of a URL2PNG defect.
3. Wait for URL2PNG capture readiness
URL2PNG documents two relevant controls: say_cheese waits until a specified DOM element exists, and delay waits a fixed number of seconds after document readiness and asset loading. The readiness signal is more deterministic when the page can add it only after the needed fonts are ready. A fixed delay is useful as a diagnostic or when the page cannot expose a readiness marker, but it is less reliable and adds capture time.
Signal readiness from a page you control
Have your page add the expected marker only after font loading has settled. For example:
async function markScreenshotReady() {
try {
await document.fonts.ready;
} finally {
const marker = document.createElement('div');
marker.id = 'url2png-cheese';
document.body.appendChild(marker);
}
}
markScreenshotReady();
document.fonts.ready resolves when the document’s font loading and layout operations have completed. The marker should represent the actual readiness condition for your page; for example, if application code swaps font families after this point, move the marker until after that change. URL2PNG’s documented switch is say_cheese=true, which waits for <div id='url2png-cheese'></div> to be available. Consult the URL2PNG Quickstart Guide for its request signing and current option details.
Test a fixed delay
If you cannot change the page to add a marker, try a small delay and compare captures. Change one setting at a time and use the same URL and viewport. Increase the delay only if the comparison shows the font consistently arrives later. An arbitrary long delay can make the request slower without fixing a failed font request.
4. Rule out a cached screenshot
URL2PNG documents unique to force a fresh screenshot by varying its value, and ttl to set the screenshot cache lifetime in seconds. Its documented default TTL is 30 days. If the page was fixed recently, use a new unique value and compare the fresh image. Adjust the TTL only when its cache behavior fits your use case; cache controls do not change how fonts load.
URL2PNG signs requests using the full query string and your secret key. When adding or changing options, use the service’s documented request-building method and ensure the token corresponds to the exact query string sent. Keep the secret key server-side; do not expose it in public client code.
5. Runnable URL2PNG request examples
These examples call the documented v6 PNG endpoint and save the response. Replace the credentials and target URL. The token must be the MD5 hex digest of the exact query string followed by the secret key, as described by URL2PNG; query parameter order and encoding must match when constructing the signature and request. For production, use URL2PNG’s official SDK or carefully follow its signing instructions.
cURL
# Build the query string and signature on a trusted server.
# TOKEN represents the URL2PNG signature for this exact query and secret.
curl --fail --location \
'https://api.url2png.com/v6/API_KEY/TOKEN/png/?url=https%3A%2F%2Fexample.com&viewport=1280x1024&say_cheese=true&unique=20261004T120000Z' \
--output screenshot.png
Python
import hashlib
import time
import urllib.parse
import requests
API_KEY = "PXXXXXXXXXXXXX"
SECRET = "S_REPLACE_WITH_PRIVATE_SECRET"
params = [
("url", "https://example.com"),
("viewport", "1280x1024"),
("say_cheese", "true"),
("unique", str(int(time.time()))),
]
query = urllib.parse.urlencode(params)
token = hashlib.md5((query + SECRET).encode("utf-8")).hexdigest()
endpoint = f"https://api.url2png.com/v6/{API_KEY}/{token}/png/?{query}"
response = requests.get(endpoint, timeout=120)
response.raise_for_status()
with open("screenshot.png", "wb") as image:
image.write(response.content)
Node.js
import { createHash } from 'node:crypto';
import { writeFile } from 'node:fs/promises';
const apiKey = 'PXXXXXXXXXXXXX';
const secret = process.env.URL2PNG_SECRET;
if (!secret) throw new Error('Set URL2PNG_SECRET in the environment');
const params = new URLSearchParams({
url: 'https://example.com',
viewport: '1280x1024',
say_cheese: 'true',
unique: String(Date.now()),
});
const query = params.toString();
const token = createHash('md5').update(query + secret, 'utf8').digest('hex');
const endpoint = `https://api.url2png.com/v6/${apiKey}/${token}/png/?${query}`;
const response = await fetch(endpoint, { signal: AbortSignal.timeout(120_000) });
if (!response.ok) throw new Error(`URL2PNG returned HTTP ${response.status}`);
await writeFile('screenshot.png', Buffer.from(await response.arrayBuffer()));
For a delay-based comparison, replace say_cheese=true with a documented value such as delay=2, and regenerate the signature for the changed query. URL2PNG documents additional options including viewport, fullpage, thumbnail_max_width, user_agent, accept_languages, custom_css_url, unique, and ttl. Use only settings relevant to the diagnosis: match viewport and user agent first, then vary readiness or cache controls.
6. Fix the page when you control it
- Use a reliable font URL and ensure the response serves the intended font file.
- Declare the family and weights the page actually uses, and verify character coverage for subsets.
- Choose a suitable
font-displaypolicy so fallback text remains acceptable while the web font loads. Chrome’s documentation describes howfont-displayaffects text visibility during font loading. - Consider preloading a critical font only when it is needed early and the preload matches the font request. An unnecessary or mismatched preload can waste bandwidth without helping the rendered page.
- Use a fallback stack with compatible metrics and appearance so a delayed or unavailable font does not make the page unusable.
For implementation background, see Chrome’s font-display guidance, WebKit’s font-loading documentation, and Google Fonts technical considerations.
7. Troubleshooting common symptoms
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser and screenshot both use fallback fonts | The font request failed, the CSS family/weight is wrong, or characters are outside font coverage. | Inspect font requests, computed styles, @font-face, and unicode-range. Fix the page before changing capture timing. |
| Browser looks correct; screenshot has fallback text | The capture may occur before font loading or later application rendering completes. | Use say_cheese with a meaningful marker, or compare a measured delay. |
| Screenshot has blank text | Some browser font-loading behavior can temporarily hide text while a font loads. | Wait for font readiness, inspect the font request, and consider the page’s font-display policy. |
| Only some letters or languages look wrong | A subset font may not cover the glyphs, or the relevant unicode-range is incorrect. |
Check the affected code points and load the correct subset or font file. |
| Changes to delay have no effect | The font is unavailable or invalid, or the returned capture is cached. | Verify the font request in a browser, force a fresh image with a new unique value, and test one variable at a time. |
| Signed request fails after adding an option | The token does not match the full encoded query string sent. | Rebuild the signature using the exact query string and URL2PNG’s signing instructions. |
| Capture differs only at a particular viewport or locale | Responsive CSS, user-agent variation, or language-specific content/font subsets may differ. | Match viewport, user_agent, and accept_languages while comparing. |
8. Performance, reliability, and cost considerations
Readiness is a tradeoff: a reliable page marker avoids guessing, while a fixed delay adds that many seconds to every capture even when the font is already ready. Keep the marker tied to the actual visual state needed for the screenshot. Avoid increasing delays to mask a broken resource, since that increases latency and still cannot guarantee the right font.
For repeatable diagnosis, save the tested URL, viewport, user agent, readiness setting, delay, and cache-busting value with each image. Change one factor per capture. A fresh capture can distinguish stale output from a current rendering issue; a cache hit cannot confirm that a recently changed page has been recaptured. The supplied URL2PNG documentation describes TTL and uniqueness controls but does not establish pricing or cost for these examples, so check your account’s current plan for billing details.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It removes cookie banners, popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. One thousand screenshots a month are free with no card; paid plans start at $5 for 3,000.
Use the one-call API when you want an image without setting up browser automation:
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
See the ScreenshotNeo API documentation for the request options. Create a free account for 1,000 screenshots a month, with no card required.
FAQ
Does a longer delay guarantee the right font?
No. It can give a slow but valid font more time to load. It cannot fix a missing URL, failed request, incorrect CSS declaration, or unsupported glyph.
Should I use say_cheese or delay?
Use a readiness marker when the page can add it at a dependable point. Use a measured delay when you cannot control the page or as a quick diagnostic.
Why does only the screenshot show the wrong font?
The screenshot may have captured an earlier loading state, reused a cached image, or rendered with a different viewport or user agent. Compare those conditions before changing font CSS.


