What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
A blank Puppeteer image is usually a symptom, not a screenshot-option bug. First prove that navigation reached the intended URL and response, then verify that Next.js rendered and hydrated the expected DOM, and only then adjust screenshot timing or the capture target. The sequence below separates failures in navigation, client rendering and capture readiness so you can apply the narrowest fix.
Start with a diagnostic capture
Save evidence before changing flags. Puppeteer navigation can complete for valid HTTP errors such as 404 or 500, so a resolved page.goto() promise does not prove that your route rendered. Record the final URL, main-document status, title, a distinctive selector or text, console output, page errors and failed requests.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
page.on('console', message => console.log('[console]', message.type(), message.text()));
page.on('pageerror', error => console.error('[pageerror]', error));
page.on('requestfailed', request =>
console.error('[requestfailed]', request.url(), request.failure()?.errorText));
const response = await page.goto('http://localhost:3000/dashboard', {
waitUntil: 'domcontentloaded',
timeout: 60000
});
console.log({
finalUrl: page.url(),
status: response?.status(),
title: await page.title(),
bodyText: (await page.locator('body').innerText()).slice(0, 500)
});
await page.screenshot({path: 'diagnostic.png', fullPage: true});
await browser.close();
If the final URL, status, title or expected text is wrong, fix routing, authentication, redirects or server output before investigating pixels. If the DOM contains the expected content but the image is blank, continue with rendering and capture checks.
Classify the failure stage
| Stage | Typical evidence | Next action |
|---|---|---|
| Navigation or response | Unexpected final URL, 404/500 status, login page, empty server response | Inspect redirects, route parameters, cookies, headers and server logs. |
| Client render or hydration | Expected server markup is replaced, console/page errors appear, content never arrives | Resolve the Next.js hydration or runtime error. |
| Capture timing | DOM eventually becomes correct, but screenshot runs earlier | Wait for an application-specific ready signal or a known element. |
| Visual target | DOM has content, but selected element has zero size, is hidden or styled transparent | Check dimensions, visibility, CSS and whether you captured the intended page or element. |
Verify navigation and the main response
Log response?.status() and page.url() after goto. A successful HTTP exchange can still deliver a 404 or error document. Also check redirects to sign-in pages and routes that require cookies or an authorization header.
#1 Best Overall
Make failures explicit
const response = await page.goto(target, {
waitUntil: 'domcontentloaded',
timeout: 60000
});
if (!response) throw new Error('No main-document response');
if (response.status() >= 400) {
throw new Error(`Navigation returned ${response.status()} at ${page.url()}`);
}
await page.waitForSelector('[data-page-ready]', {timeout: 30000});
Use the status check as a guard, not as proof of visual readiness. Some applications return a 200 shell and fetch the real content later.
Check the DOM before checking the image
Assert a stable selector, heading or data attribute that uniquely identifies the page. Avoid relying only on a generic body element: it exists even when the application failed.
const heading = await page.locator('h1').first().textContent();
const ready = await page.locator('[data-page-ready]').count();
console.log({heading, ready});
if (!heading || ready !== 1) {
await page.screenshot({path: 'unexpected-dom.png', fullPage: true});
throw new Error('Expected application content is absent');
}
When text is present in the DOM but pixels are missing, inspect the target’s bounding box and computed visibility. A full-page screenshot and an element screenshot can reveal whether the problem is global or limited to one component.
const card = page.locator('#report-card');
const box = await card.boundingBox();
console.log('box', box);
if (!box || box.width === 0 || box.height === 0) {
throw new Error('Target element has no visible dimensions');
}
await card.screenshot({path: 'report-card.png'});
Look for Next.js hydration and runtime errors
Hydration is the point at which React attaches interactivity to prerendered HTML. A hydration error means the tree produced on the server differs from the tree produced during the browser’s first render. Next.js documents several common causes:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsRank #2
- Invalid HTML nesting that the browser repairs differently from React’s tree.
- Reading browser-only APIs such as
windoworlocalStoragewhile rendering. - Time-dependent or random values that differ between server and browser.
- Browser extensions or injected markup.
- Incorrect CSS-in-JS configuration.
- An edge or CDN layer modifying the HTML.
Capture pageerror and console messages, then reproduce the route with JavaScript enabled in a normal browser. A blank image after hydration can simply be a runtime exception that leaves only the initial shell.
Prefer matching server and browser output
Move browser-only work into useEffect so the first client render matches the server. For a component that cannot be prerendered, Next.js supports a dynamic import with ssr: false. Use that scope narrowly: disabling prerendering for an entire page can hide useful server-rendered content and worsen loading behavior.
'use client';
import {useEffect, useState} from 'react';
export default function ClientValue() {
const [value, setValue] = useState(null);
useEffect(() => {
setValue(window.localStorage.getItem('value'));
}, []);
return <span>{value ?? 'Loading'}</span>;
}
suppressHydrationWarning is a narrowly scoped escape hatch for unavoidable differences. It does not make React repair mismatched text, so fixing the mismatch is preferable.
Wait for the content that matters
Puppeteer’s screenshot examples use waitUntil: 'networkidle2', and Puppeteer also provides page.waitForNetworkIdle(). These wait for network conditions, not for proof that a particular React component rendered correctly. Analytics, polling and long-lived connections can also prevent a useful idle state.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Use an application-specific selector
await page.goto(target, {waitUntil: 'networkidle2', timeout: 60000});
await page.waitForSelector('[data-page-ready]', {visible: true, timeout: 30000});
await page.screenshot({path: 'page.png', fullPage: true});
Have the page set data-page-ready only after its data and critical layout are ready. If you cannot add a marker, wait for a distinctive heading, chart container or other stable element and then allow images or fonts to settle.
Handle lazy content and delayed media
For full-page captures, scroll through the document before the final shot when lazy images are triggered by viewport entry. Wait for important images to complete:
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += 700) {
window.scrollTo(0, y);
await new Promise(resolve => setTimeout(resolve, 100));
}
window.scrollTo(0, 0);
await Promise.all([...document.images].map(img =>
img.complete ? Promise.resolve() : new Promise(resolve => {
img.addEventListener('load', resolve, {once: true});
img.addEventListener('error', resolve, {once: true});
})
));
});
await page.screenshot({path: 'full.png', fullPage: true});
This is a practical readiness technique, not a guarantee for every framework or third-party widget. Keep a finite timeout and treat failed image loads separately from an otherwise valid page.
Check viewport, CSS and the capture target
Set the viewport before navigation when responsive layout affects what renders. Confirm that dark-mode or responsive CSS is not making text blend into the background. For an element capture, verify the selector resolves to the intended instance, has non-zero dimensions and is not covered by a loading layer. Compare:
Rank #4
- A viewport screenshot, which shows what the browser paints.
- A full-page screenshot, which can expose clipping or layout height issues.
- An element screenshot, which isolates selector and visibility problems.
await page.setViewport({width: 1440, height: 900, deviceScaleFactor: 1});
await page.screenshot({path: 'viewport.png'});
await page.screenshot({path: 'full-page.png', fullPage: true});
Common blank-screenshot causes and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Image shows a 404 or sign-in page | Wrong route, redirect or missing credentials | Log final URL/status; set cookies or authorization before goto. |
| Only server shell appears | Client bundle failed or hydration crashed | Read console/page errors and failed requests; fix the Next.js mismatch or JavaScript exception. |
| Intermittent blank output | Capture races application rendering | Wait for a selector or app-ready marker instead of an arbitrary short delay. |
| Correct DOM, blank element | Zero dimensions, hidden CSS or wrong selector | Log boundingBox(), computed styles and capture the element by a verified selector. |
| Images missing below the fold | Lazy loading has not been triggered | Scroll the page, wait for image completion and then capture. |
| Navigation hangs on idle | Polling, analytics or open connections | Use domcontentloaded plus a specific readiness selector and a bounded timeout. |
Make the pipeline reliable
Keep diagnostics in production jobs
On failure, retain the final URL, status, console and page errors, request failures, a small DOM excerpt and a diagnostic screenshot. Include browser, Puppeteer, Next.js and Node versions in the job log; without those versions and the launch configuration, a particular root cause cannot be assigned confidently.
Use bounded, staged waits
Set a navigation timeout and a separate selector timeout. A staged process—navigation, DOM assertion, application readiness, media settling, capture—tells you which phase failed and avoids masking a broken page with a long sleep.
Control authentication and environment
Load required cookies or headers before navigation, use a deterministic timezone where date formatting matters, and disable extensions in headless runs. If a CDN rewrites HTML, compare the response received by Puppeteer with the origin response.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A single request returns PNG, JPEG, WebP or PDF, so you can move capture out of your Puppeteer process when you do not need browser-level debugging.
Best Value
- Used Book in Good Condition
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for parameters and response headers. The same request in Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const buffer = Buffer.from(await res.arrayBuffer());
ScreenshotNeo accepts cookie and consent banners, removes more than 60 known consent platforms plus newsletter popups and chat widgets before capture, and lets you turn each cleanup step off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed; response headers identify the page verdict and whether it was billed. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
For more control, it supports full-page and CSS-selector captures, dark mode, device presets or custom viewports, retina scale, PDF paper and page ranges, HTML/CSS input, custom JavaScript and CSS, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification. Parameter names used by other screenshot APIs also work for easier migration.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Create a free ScreenshotNeo account and test the capture without a card.
Recommended Free Tools
Frequently Asked Questions
Should I always use networkidle2 for Next.js screenshots?
No. It is a documented example, but network idleness does not prove that your component rendered. Prefer a selector or application-ready signal, with a bounded timeout.
Does suppressHydrationWarning fix a blank page?
No. It suppresses a narrowly scoped warning and does not repair mismatched text. Move browser-only work to useEffect or disable prerendering only for the component that requires it.
Why does page.goto succeed when the screenshot is an error page?
Puppeteer navigation can resolve for valid HTTP statuses including 404 and 500. Inspect the main response status and final URL before capturing.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →




