October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
JavaScript debugging

How to Fix Blank Puppeteer Screenshots of Next.js Pages

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Invalid HTML nesting that the browser repairs differently from React’s tree.
  • Reading browser-only APIs such as window or localStorage while 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • 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.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
The SQL Programming Language: .
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.