Fix Missing Images and Fonts in Browserless Screenshots
Make Browserless screenshots wait for images and fonts, trigger lazy loading, and distinguish slow assets from blocked requests.
If images or fonts are missing from a Browserless screenshot, first determine whether the asset is still loading, lazy-loaded below the fold, or failing altogether. For Browserless BAP, set waitForImages: true; for lazy images, scroll the page before capture. With the REST screenshot endpoint, set scrollPage: true and options.fullPage: true for a long page. For fonts in a connected browser session, await document.fonts.ready and check whether the font request succeeded. A longer timeout cannot fix an asset rejected by the site or a CAPTCHA page.
This guide covers Browserless BAP, REST, Puppeteer, and Playwright approaches, along with ways to inspect failures and keep capture behavior repeatable. Browserless documents waitForImages for BAP screenshots; it does not document waitForFonts as a screenshot option. Puppeteer’s waitForFonts is documented for PDF generation, while waiting on document.fonts.ready in a connected page is a browser-level readiness check. Browserless BAP screenshot documentation, Browserless REST screenshot documentation, and Puppeteer PDF options.
1. Identify what is missing
Compare the screenshot to the page in a regular browser at the same viewport. The right fix depends on where the failure happens:
| What you see | Likely class of problem | First action |
|---|---|---|
| An image appears later in a regular browser | Capture happened before it finished loading | Wait for images or a page-specific readiness condition |
| Images below the fold are absent | Lazy loading was never triggered | Scroll through the page before the full-page capture |
| Text uses a fallback font | Font is pending, failed, inaccessible, or declared incorrectly | Await font readiness, then inspect the font request and computed style |
| Whole page is blank, a challenge, or access-denied content | Navigation, automation blocking, or site access issue | Inspect the rendered page and response before increasing timeouts |
| Part of the page is missing | Capture bounds, viewport, selector, or clipping issue | Check viewport and capture scope |
Browserless lists blank captures, CAPTCHA pages, access denied or 403 pages, and missing or broken elements among symptoms that can indicate automation blocking. A missing asset by itself does not prove blocking: inspect whether the page is a challenge and whether the asset request failed.
2. Fix image timing and lazy loading
Browserless BAP: wait for images
Image waiting is opt-in in BAP and defaults to false. Enable it for image-heavy pages. A selector wait can also establish that a known component exists, but the selector appearing does not guarantee every image in that component has finished loading.
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle2" });
await page.screenshot({
path: "capture.png",
fullPage: true,
waitForImages: true
});
Use the equivalent BAP request option in the interface you use. Browserless’s troubleshooting guidance recommends waitForImages: true or waiting for a specific element when capture begins before images finish loading.
Trigger images loaded on scroll
Waiting does not necessarily cause a lazy image to load. Many pages request below-the-fold assets only after they enter or approach the viewport. Scroll down the page before capturing, then return to the desired position if the capture is viewport-only. Full-page capture determines the output area; it is not a universal substitute for triggering the page’s lazy-load behavior.
In Browserless REST, use top-level scrollPage: true, paired with options.fullPage: true when you want the entire long page. The REST API documents scrolling as a way to trigger lazy-loaded content.
Browserless REST: runnable cURL
Set BROWSERLESS_TOKEN in your shell before running this request. The response is written to capture.png.
curl -X POST \
"https://production-sfo.browserless.io/screenshot?token=${BROWSERLESS_TOKEN}" \
-H 'Cache-Control: no-cache' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com/",
"scrollPage": true,
"options": {
"fullPage": true,
"type": "png"
}
}' \
--output capture.png
Browserless REST: runnable Python
import os
import requests
TOKEN = os.environ["BROWSERLESS_TOKEN"]
endpoint = "https://production-sfo.browserless.io/screenshot"
response = requests.post(
endpoint,
params={"token": TOKEN},
headers={"Cache-Control": "no-cache"},
json={
"url": "https://example.com/",
"scrollPage": True,
"options": {"fullPage": True, "type": "png"},
},
timeout=90,
)
response.raise_for_status()
with open("capture.png", "wb") as image:
image.write(response.content)
Browserless REST: runnable Node.js
import { writeFile } from "node:fs/promises";
const token = process.env.BROWSERLESS_TOKEN;
if (!token) throw new Error("Set BROWSERLESS_TOKEN first");
const response = await fetch(
`https://production-sfo.browserless.io/screenshot?token=${encodeURIComponent(token)}`,
{
method: "POST",
headers: {
"Cache-Control": "no-cache",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/",
scrollPage: true,
options: { fullPage: true, type: "png" },
}),
},
);
if (!response.ok) {
throw new Error(`Browserless returned ${response.status}: ${await response.text()}`);
}
await writeFile("capture.png", Buffer.from(await response.arrayBuffer()));
3. Wait for fonts in a connected browser
Fonts need their own readiness check. In Puppeteer or another connected browser session, wait for the page’s font set before taking the screenshot:
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: "capture.png", fullPage: true });
document.fonts.ready resolves when the document’s font loading and layout operations have completed. It does not make a failed font request succeed. If the page still uses a fallback, inspect the browser’s network activity for the font URL, HTTP status, and console errors. Confirm the URL is reachable from the browser session and that the requested family and weight match the page’s CSS.
Do not assume that adding waitForFonts to Browserless screenshot options enables a screenshot font wait. In the referenced Puppeteer API it is a PDF option; the Browserless BAP screenshot options in the dossier list image waiting, not a screenshot font flag. If using a Browserless PDF workflow, follow its PDF documentation for PDF-specific options.
4. Wait for the page’s actual content
Navigation lifecycle events are useful checkpoints, not guarantees that every image, font, or application-rendered component is ready. Browserless examples use networkidle2 or networkidle; its REST configuration also supports waits for events, functions, selectors, and timeouts. A page can continue fetching data or hydrating after navigation.
Prefer a condition tied to the content that must appear. For example, wait for a product gallery or report container, then check its images and fonts. In a connected Puppeteer page, the following pattern waits for a selector, image completion, and font readiness before capture:
await page.goto("https://example.com", { waitUntil: "networkidle2" });
await page.waitForSelector(".product-gallery img", { timeout: 15000 });
await page.evaluate(async () => {
await document.fonts.ready;
await Promise.all(
Array.from(document.images, (image) => {
if (image.complete) return Promise.resolve();
return new Promise((resolve) => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", resolve, { once: true });
});
}),
);
});
await page.screenshot({ path: "capture.png", fullPage: true });
This waits for current document images to finish either loading or erroring; it does not trigger offscreen lazy loading or turn errors into successful loads. Scroll the page as a separate step when the site defers requests until images approach the viewport. Set timeouts to fit the site’s normal response time and your job budget, rather than waiting indefinitely.
5. Check whether requests are blocked or filtered
If waiting does not fix the output, inspect the asset request itself. Check the page’s network log and console for failed image, stylesheet, and font requests. Look for HTTP errors, inaccessible cross-origin URLs, certificate or DNS errors, incorrect paths, and content-security or authentication problems. Check the page in the same browser context and at the same viewport.
Browserless REST supports rejectResourceTypes and rejectRequestPattern. Review those settings if you configured request filtering: a rule intended to block ads or analytics can accidentally reject image, stylesheet, or font files. Temporarily remove the filter to isolate the cause.
If the rendered page is a CAPTCHA, access-denied page, or 403 response, more waiting will not restore the original assets. Browserless documents its /unblock API for bot-detection handling. Use the site’s permitted access path and verify the actual page returned before treating the screenshot as valid.
6. Set viewport and capture bounds deliberately
Responsive CSS can change both the page layout and which assets are requested. Set the intended viewport before capture, especially when comparing against a reference browser. Then select the capture region that matches the job:
fullPage: truecaptures the document’s full page area.selectorcaptures an element by selector in the Browserless REST request body.clipcaptures a fixed rectangle using coordinates and dimensions.
A selector or clip can exclude an image even when it loaded successfully. For a full-page result, lazy-loaded images may still require scrolling first. Browserless documents the REST selector at the top level of the request body and the clip rectangle within options.
7. Troubleshooting checklist
| Symptom | Cause to check | Fix |
|---|---|---|
| Images intermittently absent | Capture races image loading | Enable BAP waitForImages: true; add a page-specific readiness condition where needed |
| Only below-fold images are absent | Lazy-load trigger never ran | Scroll the page; for REST use scrollPage: true with options.fullPage: true |
| Fonts fall back despite a delay | Font request failed, was blocked, or CSS requests another family/weight | Await document.fonts.ready and inspect the actual font request and computed style |
| Images or fonts are consistently missing | Request filter, bad URL, authorization, or site response problem | Inspect the network log; review rejectResourceTypes and rejectRequestPattern |
| Screenshot contains CAPTCHA or 403 page | Automation blocking or access denied | Verify the page response and consult Browserless bot-detection guidance; a wait alone cannot fix it |
| Expected content is cut off | Viewport, selector, clip, or capture mode excludes it | Set viewport before capture and correct fullPage, selector, or clip |
| Navigation wait times out | Lifecycle event is unsuitable for a page with ongoing requests | Use a less restrictive navigation checkpoint and then wait for the actual content condition |
| Screenshot request returns an API error | Bad token, malformed JSON, or invalid endpoint configuration | Check token, JSON shape, response status, and API error body before writing the result as an image |
8. Performance, reliability, and cost considerations
Every extra wait increases capture latency. Waiting for all network activity can be slow or never settle on pages with polling, streaming, or persistent connections. Prefer the narrowest reliable condition: the required selector, image completion, and font readiness. Add scrolling only for content whose loading depends on entering the viewport.
Use a consistent viewport, capture scope, and readiness policy across repeated captures so changes reflect the page rather than timing differences. Record failures separately from successful images; a challenge page or an image request error should not be mistaken for a valid capture. Set request and navigation timeouts intentionally, and return useful errors to callers instead of silently saving an error response as a PNG.
Browserless usage and pricing depend on your account and current plan; the supplied documentation does not establish a universal per-capture cost or performance benchmark. Estimate cost from your actual capture volume and plan, including retries. Avoid retrying a known 403 or failed asset with longer waits: diagnose the request or access issue first.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF; see the API documentation.
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 banners are accepted and removed before capture; known consent platforms, newsletter popups, and chat widgets are also removed. Each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are never billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents, including Claude and Cursor, take screenshots.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card required.
10. FAQ
Does fullPage: true load every lazy image?
Not necessarily. Full-page mode sets the capture area; sites that load images when they approach the viewport may need scrolling first.
Can I fix a 403 by increasing the timeout?
No. A longer wait helps only when the page or asset is still loading. Diagnose access denial, site blocking, and rejected asset requests separately.
Does Browserless support waitForFonts for screenshots?
The cited Browserless screenshot options document image waiting, not a screenshot font-wait option. In a connected page, await document.fonts.ready; Puppeteer’s documented waitForFonts option is for PDF generation.
Why does the same page sometimes capture correctly and sometimes not?
Timing-dependent content can finish before one capture and after another. Use a content-specific readiness check and verify failed requests rather than relying on a fixed short delay.


