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

If images look correct in Chromium but are missing, blank, or styled differently in a Puppeteer PDF, start by identifying what changed: print media rules, omitted CSS backgrounds, incomplete lazy loading, or an application that had not finished rendering. page.pdf() uses print CSS by default, and its printBackground option defaults to false. The fixes below isolate each cause without masking real load failures.

1. Classify the failure before changing code

Save a screenshot or inspect the page immediately before calling page.pdf(). Compare that state with the PDF and classify the missing visual:

  • An <img> or <picture> asset: check the image URL, loading state, responsive source selection, and lazy-loading trigger.
  • A CSS background: print backgrounds are disabled unless explicitly enabled.
  • Image present, appearance wrong: print media queries or print color adjustment changed the result.
  • Only images inserted by the app are absent: PDF generation started before the application reached its own ready state.

This distinction matters: printBackground can restore a background graphic, but it is not a universal fix for a missing <img>.

2. Understand Puppeteer’s PDF defaults

Print media is used by default

Page.pdf() generates the document with the print CSS media type. A stylesheet such as @media print { img { display:none } }, a print-only width rule, or a different src selected by a media query can therefore produce a PDF that does not match the browser view.

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

If the PDF is intended to look like the screen, set screen media immediately before PDF generation:

await page.emulateMediaType('screen');
await page.pdf({ path: 'output.pdf', printBackground: true });

Use this only when screen styling is the desired output. If you are producing a print document, keep print media and correct the relevant print rules instead.

Background graphics are off unless enabled

The documented default for printBackground is false. Enable it when a visual is supplied by background-image, gradients, background colors, or another CSS background:

await page.pdf({
  path: 'output.pdf',
  printBackground: true
});

An ordinary image element still needs to load successfully; this option does not repair a broken URL, a failed request, or an image that was never inserted.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Fonts are not images

Puppeteer PDF generation waits for fonts by default (waitForFonts: true). In a background page, the documentation cautions that bringing the page to the front may be necessary for font loading to finish. That behavior does not mean images are ready. Do not use font readiness as evidence that image decoding or lazy loading has completed.

3. Wait for the page’s actual readiness

Use navigation lifecycle waits as a starting point

The official PDF guide demonstrates navigation with waitUntil: 'networkidle2'. Puppeteer defines networkidle2 as no more than two network connections for at least 500 milliseconds; networkidle0 uses zero connections for that minimum interval:

await page.goto(url, { waitUntil: 'networkidle2' });

You can also wait after navigation:

await page.waitForNetworkIdle({ idleTime: 500 });

waitForNetworkIdle() waits at least the configured idle time. These are synchronization points, not a guarantee that every framework task, lazy image, or deferred decode has completed. Analytics, polling, service workers, and long-lived connections can also make a strict idle condition unsuitable.

Wait for an application signal

Prefer a concrete readiness signal supplied by the page: a selector such as [data-render-complete="true"], a known hero image, or an application event exposed for automation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.waitForSelector('[data-render-complete="true"]', {
  timeout: 30000
});

If the site has no signal, wait for the actual image elements in page context. The following diagnostic helper checks completion and natural dimensions, then waits for load or error outcomes. Adapt it for your page’s <picture> sources and lazy-loading mechanism:

await page.evaluate(async () => {
  const images = [...document.images];
  await Promise.all(images.map(img => {
    if (img.complete) return Promise.resolve();
    return new Promise(resolve => {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
  const failed = images.filter(img => !img.naturalWidth);
  if (failed.length) {
    throw new Error(`Images without usable pixels: ${failed.length}`);
  }
});

img.complete only says that loading finished (successfully or unsuccessfully); naturalWidth helps distinguish a usable decoded resource from a failed one. For lazy images, scroll or trigger the component’s documented load action before this check. For CSS backgrounds, inspect the computed style and wait for the page’s own render-complete condition.

4. A complete Puppeteer pattern

This example combines the diagnostics without assuming that network idle alone is sufficient. Replace the URL and readiness selector with values from your application.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await page.goto('https://example.com/report', {
    waitUntil: 'networkidle2',
    timeout: 60000
  });

  // Use this when the PDF should match screen media.
  await page.emulateMediaType('screen');

  // Prefer your app's signal over a generic delay.
  await page.waitForSelector('[data-render-complete="true"]', {
    timeout: 30000
  });

  await page.evaluate(async () => {
    const images = [...document.images];
    await Promise.all(images.map(img => {
      if (img.complete) return Promise.resolve();
      return new Promise(resolve => {
        img.addEventListener('load', resolve, { once: true });
        img.addEventListener('error', resolve, { once: true });
      });
    }));
    const failed = images.filter(img => !img.naturalWidth);
    if (failed.length) throw new Error(`Failed images: ${failed.length}`);
  });

  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    waitForFonts: true,
    preferCSSPageSize: true
  });
} finally {
  await browser.close();
}

If print styling is intentional, remove emulateMediaType('screen') and fix the print stylesheet. If the page has no readiness selector, use a targeted predicate or a short, justified delay after triggering lazy loading; avoid an arbitrary long sleep as your only synchronization mechanism.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

5. Troubleshooting by symptom

The PDF has no colored panels or decorative graphics

Those are often CSS backgrounds. Set printBackground: true. Also check whether print CSS deliberately removes them.

An <img> is present in the DOM but blank

Inspect its src/currentSrc, request status, and naturalWidth. A relative URL may resolve against an unexpected base, an authenticated endpoint may reject Chromium, or a responsive image may select an unavailable source. Fix the request or credentials, then wait for load before calling pdf().

Only images below the fold are missing

The page likely uses lazy loading. Trigger the component’s loading behavior (often by scrolling through the document), wait for its completion signal, and then verify each relevant image. Network idle can occur before an intersection observer has requested those assets.

The browser screenshot is correct but the PDF is not

Compare media modes first. If screen appearance is required, call page.emulateMediaType('screen'). Otherwise inspect @media print rules and print-specific dimensions.

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

Colors look washed out or different

Print rendering can modify colors. When exact colors are required, use CSS -webkit-print-color-adjust: exact; on the relevant elements or document, while understanding that color management and printer-oriented styling still apply.

Network-idle waiting hangs or finishes too soon

Use networkidle2 for pages with a few persistent connections, or a targeted readiness selector. Do not assume networkidle0 is practical for applications with polling or streaming. Conversely, a page can reach either idle state while a framework still schedules image work, so combine lifecycle waiting with an app-specific check.

Images work locally but fail in production

Check deployment-specific URLs, certificates, authentication headers, cookies, user-agent behavior, content-security policies, and the Chromium revision used by the deployed Puppeteer version. Log failed image URLs and response statuses from the page rather than treating the PDF as the first diagnostic surface.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

6. A practical diagnostic checklist

  1. Reproduce the PDF and inspect the page immediately before page.pdf().
  2. Decide whether the missing visual is an image element, a CSS background, or print-only styling.
  3. Enable printBackground for backgrounds.
  4. Choose print or screen media deliberately.
  5. Wait for navigation activity, then wait for the application’s render-complete condition.
  6. Check image complete, naturalWidth, selected source, and failed requests.
  7. Trigger lazy loading and verify below-the-fold assets.
  8. Account for print color adjustment when pixels exist but colors differ.
  9. Record the Puppeteer version, browser revision, URL, viewport, and relevant headers so a production failure can be reproduced.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server when you need a rendered capture without maintaining Puppeteer. One GET request returns PNG, JPEG, WebP, or PDF:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 request options. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots, and every feature is included on every plan. Create a free ScreenshotNeo account.

7. Other clients for the same endpoint

Python

import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

FAQ

Does waitForFonts wait for images?

No. It concerns font loading; image readiness requires its own checks.

Should I always use networkidle0?

No. Select a lifecycle wait that fits the site’s connections and add an application-specific readiness condition.

Is printBackground required for every image?

No. It targets CSS background graphics. An <img> must still load and decode successfully.

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.

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.