ScreenshotNeo

BlogHow-to

How to Fix Images Not Loading in Browserless Screenshots

Fix missing images in Browserless screenshots by waiting for images, triggering lazy loading, and checking failed requests or blocked pages.

By the ScreenshotNeo team4 October 20268 min read

If images are missing from a Browserless screenshot, first make sure the capture waits for them: enable waitForImages: true in BAP, or wait for a specific image or container with waitForSelector(). For images below the fold, scroll through the page before a full-page capture so lazy loading can start. Then check whether image requests failed, were filtered, or returned a bot check or access-denied page.

Waiting for navigation and waiting for images are separate tasks. A page can reach domcontentloaded while its images are still loading, and a successful screenshot API response does not prove the target page rendered successfully.

1. Identify which kind of image failure you have

Before adding a longer delay, classify the symptom. The fix depends on whether the browser captured too early, never triggered lazy loading, could not fetch the image, or received a blocked or error page from the target site.

What you see Likely cause First check
Images appear after waiting or refreshing in a browser Capture happened before image loading finished Enable waitForImages or wait for the important image selector
Images near the top appear, but lower images do not Viewport-triggered lazy loading was never activated Scroll through the page before full-page capture
Broken image placeholders appear at any scroll position Image request failed, was filtered, or was blocked Inspect request filters and the target page response
The image area is part of a blank, CAPTCHA, or access-denied page The target site served an automation challenge or error Inspect the actual rendered page; waiting longer may not help

Confirm that the images exist at the target URL and viewport in a normal browser. If an image is inserted only after a user interaction, or the page shows a different version to automated traffic, capture timing alone will not explain the result.

2. Wait for images or a specific element

In Browserless BAP, waitForImages is false by default. Turn it on when the goal is to wait for all page images. If only one image or component matters, wait for its selector instead; that makes the readiness condition specific to the content you need.

const browser = await puppeteer.connect({ browserWSEndpoint: BROWSERLESS_ENDPOINT });
const page = await browser.newPage();

await page.goto('https://example.com', { waitUntil: 'load' });
await page.waitForSelector('.product-gallery img', { visible: true });

const imageState = await page.$eval('.product-gallery img', (img) => ({
  src: img.currentSrc || img.src,
  complete: img.complete,
  naturalWidth: img.naturalWidth,
}));

if (!imageState.complete || imageState.naturalWidth === 0) {
  throw new Error(`Product image did not load: ${imageState.src}`);
}

await page.screenshot({
  path: 'screenshot.png',
  fullPage: true,
  waitForImages: true,
});

await browser.close();

Replace BROWSERLESS_ENDPOINT with the Browserless connection endpoint for your account, and change the URL and selector to match your page. The check for naturalWidth catches an img element that exists in the DOM but has no successfully decoded image. For CSS background images, inspect the relevant element’s computed style or wait for a visible component state; img-specific checks do not cover backgrounds.

Use an explicit selector wait when an image is added asynchronously or when unrelated images make an all-images wait too broad. A selector becoming visible confirms the element is present and visible, but does not by itself guarantee its image bytes loaded; check complete and naturalWidth where that distinction matters.

3. Trigger lazy loading below the fold

Many pages load images only when they approach the viewport. A full-page screenshot does not necessarily cause the page’s own lazy-loading code to behave as if a visitor scrolled through it. Scroll down the document before capture, then take the full-page screenshot.

await page.goto('https://example.com', { waitUntil: 'load' });

await page.evaluate(async () => {
  const step = Math.max(window.innerHeight, 400);
  for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
    window.scrollTo(0, y);
    await new Promise((resolve) => setTimeout(resolve, 150));
  }
  window.scrollTo(0, 0);
});

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

The short pause gives viewport observers a chance to run at each scroll position; it is a practical trigger, not proof that every image request has completed. Keep waitForImages or a targeted image check if completion matters. Browserless BAP also documents scroll({ throughPage: true }) for scrolling through a page.

4. Choose the right readiness condition

Navigation conditions answer different questions. They are not interchangeable with an image-specific wait.

Condition or option What it tells you Use it when
domcontentloaded Initial HTML parsing finished; stylesheets, images, and subframes may still be loading You intend to wait separately for a selector or other condition
load The document and dependent resources, including images, finished loading A conventional page load is a useful baseline for the capture
networkidle0 No network connections for at least 500 ms The page settles and does not keep background requests active
networkidle2 At most two network connections for at least 500 ms A page has a small amount of ongoing network activity
waitForImages: true Requests that the screenshot wait for images to finish You need broad image completion in BAP
waitForSelector() Waits for a particular DOM element or state One image or component is the actual capture requirement

Pages with analytics, polling, streaming, or long-lived requests can make network-idle conditions unsuitable. Conversely, load can wait on resources that are not relevant to your screenshot. Choose the narrowest condition that establishes the state you need, and add image checks for critical assets.

5. Check request filters and target-site responses

Browserless request configuration can reject resource types or request patterns. Review rejectResourceTypes and rejectRequestPattern to ensure they do not match images or the image host. A broad rule aimed at reducing page weight can silently remove the very resources your screenshot needs.

If filters are not responsible, inspect the page and its image requests. Look for failed network requests, access-denied content, a CAPTCHA, a blank page, or an error message from the target site. An HTTP 200 from the screenshot service can still contain an error returned by the target website. Browserless documents an /unblock endpoint for bot-detection scenarios; consult its current documentation for applicable behavior and requirements.

6. Apply the setting for your Browserless interface

Browserless has multiple capture interfaces, and option names differ. BAP documents screenshot options including waitForImages and its scrolling API. The REST screenshot API uses request settings such as scrollPage: true and shared waiting configuration. BrowserQL has a waitForImages screenshot argument. Check the documentation for the interface and version you actually call before transferring an option from one example to another.

For the REST screenshot API, the practical sequence is to configure an appropriate navigation wait, set scrollPage: true when below-the-fold lazy content is needed, and use the documented wait configuration for the specific page state. If the images remain missing, check request filtering and inspect the target response rather than increasing a fixed delay repeatedly.

7. Troubleshooting checklist

Problem Cause to investigate Fix
Top images load, lower images are absent Lazy loading is waiting for the images to enter the viewport Scroll through the page; enable REST scrollPage: true or use BAP scrolling, then capture full page
Capture ends while images are still appearing Navigation completed before image requests Enable BAP waitForImages: true, use load, or wait for the target selector and verify image state
waitForImages seems to have no effect Option belongs to a different Browserless interface or is not configured in the expected screenshot call Check the exact API, version, and option location in that interface’s reference
Image element exists but the bitmap is broken Image request failed, URL is invalid, or access to the image host was denied Check currentSrc, request results, and target-site response
All images are consistently absent A rejected resource type or URL pattern matches them Review rejectResourceTypes and rejectRequestPattern
Screenshot contains a CAPTCHA or access-denied page Target site is blocking automated access Diagnose the returned page and consult Browserless’s /unblock guidance
Screenshot request times out Page or resource loading did not settle within the configured timeout Inspect load state and pending image requests; narrow waits to the required content. BAP’s screenshot reference lists a 30-second default timeout
Longer waits make the capture slow without fixing it Failure is caused by filtering, a broken URL, or target blocking rather than timing Inspect the failed request and rendered page; remove arbitrary delay once the cause is addressed

8. Performance and reliability notes

  • Waiting for every image can increase capture time when a page has many images or slow third-party hosts. Prefer a target selector and an image-state check when only one asset matters.
  • Scrolling through a long page takes time and triggers additional requests. Use it only when below-the-fold content is part of the requested output.
  • Network-idle waits can stall on pages with persistent background traffic. A selector tied to the needed content can be more predictable.
  • Use a finite timeout and report which readiness check failed. A timeout should prompt investigation of response state, not an automatic assumption that still more waiting will solve the problem.
  • For repeatable captures, keep the URL, viewport, wait condition, scroll behavior, and resource filters explicit. This makes differences between runs easier to diagnose.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Its screenshot API can capture a URL in one GET request, and its documented full-page capture loads lazy images. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; those steps can be turned off. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers.

For an image file, make this request with your ScreenshotNeo API key. See the ScreenshotNeo API documentation for options and setup.

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

The same request in Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in Node.js:

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', new Uint8Array(await res.arrayBuffer()));

ScreenshotNeo also has an MCP server so Claude, Cursor, and other MCP clients can take screenshots with the take_screenshot tool. It includes get_page_info and capture_pdf as well. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000, and every feature is available on every plan.

Sign up for ScreenshotNeo free for 1,000 screenshots a month with no card.

10. FAQ

Does a full-page screenshot automatically load lazy images?

Do not assume so. Trigger lazy loading by scrolling through the page, then capture the full page.

Is waitForImages the same as networkidle0?

No. One targets image completion in the screenshot flow; the other waits for a period with no network connections. Their behavior and scope differ.

Why does the screenshot service return success but show an error page?

The service can successfully return an image of a page that itself contains a CAPTCHA, access denial, or target-site error. Inspect the captured page content.

Should I always use a fixed delay?

No. A delay may help diagnose a timing issue, but a selector or image-state check is a clearer readiness condition. A delay will not repair blocked or filtered requests.

References