How to Troubleshoot Browserless Screenshots That Ignore CSS
Fix unstyled Browserless screenshots by checking page state, navigation waits, CSS filters, viewport, and PDF settings—with runnable examples and a diagnostic checklist.
If a Browserless screenshot ignores CSS, first confirm the target page loaded successfully, then check that your request does not block stylesheet files and that capture waits for styles and the page’s final rendered state. domcontentloaded only confirms the initial HTML parse; it does not wait for stylesheets. For an image screenshot, try load or a suitable network-idle wait, then wait for an application-specific ready marker if the page renders with JavaScript.
This guide focuses on Browserless’s Puppeteer, Playwright, and REST screenshot surfaces. Check the exact option names supported by the endpoint and client you use; similar concepts can have different spellings.
1. Confirm the target page is the page you meant to capture
Do not assume a successful Browserless API response means the target site rendered successfully. The screenshot can contain a target-site 403, 404, or 500 page even when the API request itself returned HTTP 200. Inspect the captured content for redirects, consent screens, blank pages, CAPTCHA challenges, and access-denied messages before changing timing settings. Browserless identifies blank or white screenshots, CAPTCHA pages, and 403/access-denied screens as possible signs of automation blocking. See the Browserless troubleshooting guidance.
If the capture shows a challenge or error page, repeatedly increasing the CSS wait is unlikely to solve the underlying problem. Diagnose the target response and automation blocking separately from stylesheet timing.
2. Use a navigation wait that includes stylesheets
Browserless documents that domcontentloaded fires after the initial HTML is parsed without waiting for stylesheets, images, or subframes. Its load event waits for dependent resources, including stylesheets. For pages whose assets settle normally, change a too-early navigation wait to load.
| Wait condition | What it helps establish | Use it when |
|---|---|---|
domcontentloaded |
The initial HTML has been parsed. | You need the document structure early and handle readiness separately. It does not establish that CSS loaded. |
load |
Dependent resources, including stylesheets, have loaded. | The page’s CSS and other resources finish loading as part of navigation. |
networkidle0 |
No active network connections for at least 500 ms. | The page becomes quiet and has no persistent connections that prevent idleness. |
networkidle2 |
No more than two active connections for at least 500 ms. | The page keeps a small amount of background network activity open. |
Network idleness is not proof that a JavaScript application reached its final visual state. A page can become briefly quiet before it applies data or styles, or remain active because of analytics or polling. Use a meaningful selector or application-ready condition in addition to navigation when that better represents the desired state. Browserless describes these lifecycle events and wait options in its browser navigation documentation and Screenshot API guide.
Puppeteer example
import puppeteer from 'puppeteer';
const browser = await puppeteer.connect({
browserWSEndpoint: process.env.BROWSERLESS_WS,
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
const response = await page.goto('https://example.com', {
waitUntil: 'load',
timeout: 60000,
});
console.log('Target status:', response?.status());
await page.waitForSelector('[data-page-ready="true"]', { timeout: 30000 });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Replace the example URL and ready selector with values for your page. If there is no reliable ready marker, use the strongest navigation wait the page supports and investigate the relevant stylesheet requests.
Playwright example
import { chromium } from 'playwright';
const browser = await chromium.connectOverCDP(process.env.BROWSERLESS_WS);
try {
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
});
const response = await page.goto('https://example.com', {
waitUntil: 'load',
timeout: 60000,
});
console.log('Target status:', response?.status());
await page.locator('[data-page-ready="true"]').waitFor({ timeout: 30000 });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Browserless examples use Puppeteer’s networkidle2 and Playwright’s networkidle navigation pattern. Match the spelling and behavior to the client and Browserless product surface you actually use; do not copy a wait option across clients without checking its API.
3. Wait for the final application state
For client-rendered pages, navigation may finish before the application applies its final styles or data. Wait for an element that appears or changes only when the desired visual state is ready. A selector that exists in the initial HTML is a weak readiness signal if the page fills it later.
For a Browserless REST request, the screenshot configuration supports waitForSelector. The Screenshot API guide also documents waits for events, functions, selectors, and timeouts. Choose a marker tied to the final state—for example, a report panel that appears after data loads—rather than an unrelated header that is present immediately.
{
"url": "https://example.com/dashboard",
"waitFor": "networkidle2",
"waitForSelector": {
"selector": "[data-page-ready='true']",
"timeout": 30000
}
}
Use the property names and request format for the Browserless endpoint you call; REST configuration is not interchangeable with Puppeteer or Playwright method options. A fixed delay can help determine whether a race is involved, but it is a blunt diagnostic. Keep one only when the page has a known timing behavior and no dependable ready condition.
4. Check whether request filters block CSS
Review resource and request filters before changing page code. Browserless supports rejectResourceTypes and rejectRequestPattern; its documentation includes an example that rejects CSS requests. If your request rejects the stylesheet resource type or matches stylesheet URLs, an unstyled page is the expected result.
// Puppeteer: inspect requests while diagnosing stylesheet delivery
page.on('request', request => {
if (request.resourceType() === 'stylesheet' || /\.css(?:\?|$)/i.test(request.url())) {
console.log('Stylesheet request:', request.url(), request.resourceType());
}
});
page.on('requestfailed', request => {
if (request.resourceType() === 'stylesheet' || /\.css(?:\?|$)/i.test(request.url())) {
console.log('Stylesheet failed:', request.url(), request.failure()?.errorText);
}
});
Temporarily remove the relevant rejection rule, capture again, and compare. If CSS injection is intentional—for example, you are adding a test stylesheet—Browserless’s Screenshot API supports addStyleTag with CSS content or a stylesheet URL. Injection can add or replace styles deliberately; it does not diagnose why the original stylesheet failed to load. Refer to the Browserless Screenshot API configuration.
5. Check viewport, capture scope, and lazy content
A page may be styled correctly but look different because the capture uses another responsive breakpoint or a different region. Set the viewport explicitly, including the device scale factor if relevant, and verify whether you are capturing the viewport, a selector, a clip, or the full page. Browserless’s BAP guidance recommends setting the viewport for responsive layouts.
Missing images and below-the-fold content can make a page seem incomplete even when its CSS is present. Browserless BAP documents that waitForImages defaults to false and recommends enabling it or waiting for a target selector for late images. Lazy-loaded content below the fold may need scrolling to trigger its requests; the REST Screenshot API documents scrollPage: true for this use.
// BAP-style options: check the exact fields for your Browserless surface
{
"url": "https://example.com",
"viewport": { "width": 1440, "height": 1000 },
"waitForImages": true,
"fullPage": true
}
For REST screenshot captures, use the documented scrolling option where appropriate:
{
"url": "https://example.com/long-page",
"scrollPage": true,
"fullPage": true
}
These examples illustrate separate Browserless capture surfaces; verify each field against the endpoint you use. Avoid enabling every wait or full-page option by default: they can increase capture time and are unnecessary for a viewport-only image with no lazy content.
6. If only the PDF looks wrong, inspect print behavior
PDF generation follows Chrome’s print pipeline, which can apply print stylesheets and differs from an image screenshot. Browserless documents that print backgrounds are omitted unless enabled. If a PDF loses colored backgrounds, enable printBackground. If print CSS itself creates the wrong layout and the PDF should resemble screen output, emulate the screen media type before generating it.
// Puppeteer PDF using screen styles and background graphics
await page.emulateMediaType('screen');
await page.pdf({
path: 'page.pdf',
printBackground: true,
format: 'A4',
});
Use these settings only for PDF output. They do not repair missing CSS in a PNG or JPEG screenshot. See Browserless’s PDF and browser rendering documentation.
7. Troubleshooting by symptom
| Symptom | Likely cause | What to change or inspect |
|---|---|---|
| Page is entirely unstyled | Capture happens at domcontentloaded, or CSS requests are filtered or failing. |
Try load; inspect stylesheet requests and failures; remove CSS rejection rules. |
| Some components are unstyled or appear briefly | JavaScript has not reached the final state, or styles are loaded or applied later. | Wait for a selector tied to the final content; inspect late stylesheet requests; use a page-side ready condition supported by your client. |
| Capture is blank, a CAPTCHA, or access denied | The target site may block automation or return a challenge/error page. | Inspect the actual page and target status. Follow Browserless’s anti-bot troubleshooting rather than adding longer CSS waits. |
| Layout differs from a normal browser window | Viewport dimensions, scale, responsive breakpoint, or capture region differs. | Set viewport and device scale explicitly; confirm viewport, selector, clip, or full-page capture scope. |
| Images or lower sections are missing | Image requests are late, or lazy loading has not been triggered. | Enable image waiting where supported; wait for a relevant selector; scroll to trigger below-fold content. |
| Only the PDF loses backgrounds or changes layout | Print backgrounds are off, or print CSS is active. | Enable printBackground; emulate screen media when screen styling is the intended PDF appearance. |
| Network-idle wait times out | Persistent connections, polling, or analytics prevent the connection threshold from being reached. | Try networkidle2 where supported, use load, or wait for an app-specific selector instead. |
| Longer fixed delay changes nothing | The stylesheet is blocked, the target is an error page, or the wrong region/media is captured. | Inspect the rendered page, CSS requests, viewport, capture region, and PDF media settings before adding more delay. |
8. A short verification checklist
- Confirm the captured page is the intended target, not an error, challenge, redirect, or consent wall.
- Check the target response and inspect failed stylesheet requests.
- Remove or adjust filters that reject stylesheets.
- Replace a premature
domcontentloadedwait withloador a suitable network-idle wait. - Wait for an application-specific marker that corresponds to the final visible content.
- Set the responsive viewport and confirm the capture scope.
- For late imagery, enable supported image waits or trigger lazy loading by scrolling.
- If the output is PDF, check print backgrounds and media emulation separately.
9. Performance, reliability, and cost considerations
Waiting for load can take longer than waiting for HTML parsing because it includes dependent resources such as stylesheets. Network-idle waits can take longer or time out when a page keeps background connections open. A meaningful ready selector often avoids waiting for unrelated activity, while a fixed timeout adds latency on every capture and still cannot prove readiness.
For repeatable captures, keep viewport, device scale, wait condition, and capture scope explicit. Log the target response status and failed stylesheet URLs while diagnosing. Avoid blocking CSS and avoid broad network-idle waits when the application has a clearer completion signal. Browserless’s documentation does not provide a benchmark for this specific CSS issue, so choose waits based on the target page’s observed behavior rather than assuming a universal fastest setting.
10. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.
Frequently asked questions
Does CSS need to be in the original HTML for Browserless to capture it?
No. Stylesheets can load as dependent resources after the initial document parse. The key is to allow their requests and wait for the page state you need.
Should I always use network idle?
No. Use it when network quiet corresponds to readiness for that page. Persistent connections and polling can make it slow or prevent it from completing; a meaningful selector may be more reliable.
Why does my screenshot look incomplete when the text styles are present?
Images or below-the-fold sections may still be lazy-loaded. Image readiness and CSS readiness are separate concerns.
Why does the PNG look right while the PDF does not?
PDF output uses print rendering rules. Check the print stylesheet, background printing, and media emulation rather than changing image screenshot waits.


