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

Call page.pdf() only after the application has finished its final canvas draw. The reliable pattern is to navigate with an appropriate readiness condition, expose an application signal such as window.canvasReady = true, wait for that signal with page.waitForFunction(), wait for fonts, choose the intended media type, and then generate the PDF. A fixed sleep can work by accident, but it cannot prove that asynchronous data, images, or fonts are ready.

You normally do not need to convert a canvas to an image: Chromium can print a completed canvas directly. Convert it to an <img> only when another part of your pipeline requires an image resource that can be loaded and checked independently. For large canvases, use toBlob() and an object URL rather than building a large base64 data URL.

The dependable capture sequence

  1. Navigate: use page.goto(url, { waitUntil: 'networkidle2' }) as a useful network baseline, not as proof that your application has finished drawing.
  2. Signal completion in page code: set a flag only after every data request, source-image decode, and canvas draw has completed.
  3. Wait for the signal: call page.waitForFunction(() => window.canvasReady === true).
  4. Wait for fonts: await document.fonts.ready; font readiness does not imply canvas readiness.
  5. Choose print behavior: use screen media when the PDF should match screen CSS, and enable backgrounds when colors or backgrounds are part of the design.
  6. Generate the PDF: call page.pdf() only after those conditions succeed.

networkidle2 waits for a period with no more than two active network connections. Applications can still be processing a response, decoding an image, measuring fonts, or drawing after that point, so an application-level signal remains the decisive condition.

Expose a canvas-ready signal in the page

Put the signal next to the code that knows when rendering is complete. It should start false (or be absent) and become true only after the last asynchronous operation and final draw.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
<canvas id='chart' width='800' height='400'></canvas>
<script>
  const canvas = document.querySelector('#chart');
  const ctx = canvas.getContext('2d');
  window.canvasReady = false;

  async function renderChart() {
    // Await data and every image used by drawImage() here.
    const response = await fetch('/api/chart-data');
    const data = await response.json();

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#245';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    // Draw the rest of the chart from data.
    drawBars(ctx, data);

    // This assignment must be the final step, not the start of rendering.
    window.canvasReady = true;
  }

  renderChart().catch(error => {
    window.canvasReady = false;
    console.error('Chart rendering failed', error);
  });
</script>

If your application has several canvases, use a single readiness signal after all of them finish, or expose separate signals and wait for all of them. If rendering can be restarted, reset the signal to false before starting the next render and set it true only for the new final state.

Complete Puppeteer PDF example

The following Node.js script accepts a page URL and writes output.pdf. The page itself must provide the canvasReady flag shown above.

const puppeteer = require('puppeteer');

(async () => {
  const url = process.argv[2] || 'https://example.com/report';
  const browser = await puppeteer.launch({ headless: 'new' });
  try {
    const page = await browser.newPage();
    page.setDefaultNavigationTimeout(30000);

    await page.goto(url, { waitUntil: 'networkidle2' });
    await page.waitForFunction(
      () => window.canvasReady === true,
      { timeout: 30000 }
    );

    await page.evaluate(() => document.fonts.ready);
    await page.emulateMediaType('screen');

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

Run it with node capture.js https://your-site.example/report. If your installed Puppeteer version does not expose a particular PDF option, remove that option and keep the explicit document.fonts.ready wait. Always close the browser in a finally block so a timeout does not leave Chromium processes running.

Use a stronger readiness condition when possible

A boolean flag is simple, but an application can expose a more descriptive state such as window.renderState = 'ready' or a promise resolved by the final renderer. The important property is that the condition is tied to your real completion event. Do not set it from a timer, immediately after starting a fetch, or before an image’s load event.

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.
Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.

When to replace the canvas with an image

Direct printing is the least expensive path: it avoids serialization and keeps the canvas in place. Conversion is useful when a downstream PDF or document workflow expects ordinary image elements, when you need an independently observable image-load event, or when you want to remove the canvas-specific code before printing.

Approach Readiness control Memory and speed Cross-origin behavior Print fidelity
Print the canvas directly Wait for your application signal; the PDF call captures the current bitmap. No serialization copy, usually the simplest and fastest path. Foreign pixels can still taint the canvas if you later need to export them. Subject to print media and PDF color settings.
Replace with an <img> from toBlob() Wait for the replacement image’s load event before PDF generation. Blob avoids the large in-memory base64 string created by toDataURL(); an object URL still consumes image memory until revoked. Export fails if the canvas is origin-unclean; CORS must be correct before drawing. An ordinary image resource can be checked independently, while CSS print rules still apply.
Replace with a toDataURL() image Set src and wait for load. Convenient for small images, but the encoded string can be very large. Subject to the same origin-clean requirement. Same print-media behavior as any image element.

Convert a finished canvas with toBlob()

This code runs in the page after canvasReady is true. It keeps the object URL alive until after page.pdf(), then revokes it.

await page.evaluate(async () => {
  const canvas = document.querySelector('#chart');
  if (!canvas) throw new Error('Canvas #chart was not found');
  if (canvas.width === 0 || canvas.height === 0) {
    throw new Error('Canvas has zero width or height');
  }

  const blob = await new Promise(resolve => {
    canvas.toBlob(resolve, 'image/png');
  });
  if (!blob) throw new Error('Canvas could not be encoded');

  const image = new Image();
  image.alt = 'Rendered chart';
  const objectUrl = URL.createObjectURL(blob);
  image.src = objectUrl;

  await new Promise((resolve, reject) => {
    image.onload = resolve;
    image.onerror = () => reject(new Error('Replacement image failed to load'));
  });

  canvas.replaceWith(image);
  window.canvasObjectUrl = objectUrl;
});

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

await page.evaluate(() => {
  if (window.canvasObjectUrl) {
    URL.revokeObjectURL(window.canvasObjectUrl);
    delete window.canvasObjectUrl;
  }
});

Do not revoke the object URL immediately after assigning src; the browser may not have decoded it yet. Revoke it after PDF generation, or when the page will remain open and no longer needs the image.

Canvas export rules that commonly cause missing images

Zero dimensions

A canvas whose width or height is zero has no useful bitmap. Check the element’s intrinsic width and height, not only its CSS size. A CSS width can make a nonzero canvas appear large while the underlying bitmap remains zero or very small.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.

Tainted pixels and CORS

If drawImage() uses an image from another origin without an appropriate CORS response, the canvas becomes origin-unclean. Calling toDataURL() or toBlob() can then throw a SecurityError (or fail to produce a blob). Fix the source rather than trying to bypass the browser: serve it with an Access-Control-Allow-Origin policy that permits the page, set img.crossOrigin before assigning src, or proxy the asset through the same origin.

const image = new Image();
image.crossOrigin = 'anonymous';
image.src = 'https://assets.example.com/chart-background.png';
await new Promise((resolve, reject) => {
  image.onload = resolve;
  image.onerror = reject;
});
ctx.drawImage(image, 0, 0);

Setting crossOrigin after src is too late. The server must also return a compatible CORS header; the client setting alone is not sufficient.

Fonts and print media

page.pdf() uses print media by default. Print styles can hide elements, change dimensions, or alter colors. Call page.emulateMediaType('screen') when the screen stylesheet is the intended design. Set printBackground: true when CSS backgrounds must appear, and use -webkit-print-color-adjust: exact in print CSS when exact color reproduction is required. Waiting for fonts prevents late text reflow, but it does not tell Puppeteer that your canvas JavaScript has finished.

Troubleshooting checklist

Symptom Likely cause Fix
PDF contains a blank canvas page.pdf() ran before the last draw. Set the readiness flag after the final draw and wait with page.waitForFunction(); do not substitute a guessed delay.
Canvas is present but chart data is missing Data fetch or image decode was still pending when the flag was set. Await every fetch, image onload, and decode promise before setting canvasReady.
waitForFunction times out The page never sets the flag, sets it under a different name, or rendering failed. Inspect the page console, confirm the URL is correct, add an error state, and verify the flag in DevTools or with page.evaluate(() => window.canvasReady).
toBlob() returns null or export throws SecurityError Zero dimensions or a tainted canvas. Validate dimensions and correct CORS; set crossOrigin before src or serve/proxy the image with permission.
Colors or backgrounds differ from the browser PDF is using print media or suppressing backgrounds. Choose screen media deliberately, set printBackground: true, and apply print color adjustment where needed.
Text shifts even though the canvas is ready Web fonts loaded after layout. Await document.fonts.ready and retain waitForFonts: true where supported.
Replacement image is blank Object URL was revoked too early or its load failed. Wait for image.onload, keep the URL until after PDF generation, then revoke it.
Chromium processes remain after failures Browser shutdown was skipped on an exception. Put capture and PDF calls in try and close the browser in finally.

Reliability and performance practices

  • Use bounded waits: set navigation and readiness timeouts so a failed page produces a diagnosable error instead of hanging indefinitely.
  • Prefer events over sleeps: a fixed delay must be long enough for the slowest run and is still nondeterministic; an explicit signal finishes as soon as the real work is done.
  • Keep the page stable: finish animation or freeze it for capture. If a chart continuously redraws, signal readiness at a known frame.
  • Check intrinsic resolution: set canvas dimensions deliberately. A high-DPI display can make a CSS-sized canvas look sharp on screen while the PDF receives a low-resolution bitmap.
  • Minimize serialization: print directly when possible. Use toBlob() for large images and avoid toDataURL() unless the image is small or a data URL is specifically required.
  • Capture the same state every time: provide deterministic data, timezone, locale, and viewport settings when visual output is compared in tests.
  • Log the phase that failed: record navigation, readiness, font wait, conversion, and PDF generation separately so a timeout is actionable.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you only need a clean screenshot or PDF of a URL, ScreenshotNeo provides a GET-based capture API and an MCP server for AI agents. Its cleanup step accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

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

For a one-call image capture, see the ScreenshotNeo API documentation:

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
curl -G 'https://api.screenshotneo.com/v1/shot' 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://example.com/report 
  -o shot.webp

The same endpoint supports PNG, JPEG, WebP, or PDF output and options such as full-page capture, element selectors, device and retina settings, custom CSS or JavaScript, waits for selectors or network idle, request blocking, cookies and headers, timezone and geolocation, caching, signed links, asynchronous webhooks, and bulk capture. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—let Claude, Cursor, or another MCP client perform captures without you wiring Puppeteer into the agent.

Python:

import requests

r = requests.get(
    'https://api.screenshotneo.com/v1/shot',
    params={'access_key': 'YOUR_API_KEY', 'url': 'https://example.com/report'},
    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://example.com/report'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

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 to try it.

FAQ

Does Puppeteer preserve a canvas as vector graphics in a PDF?

No. A canvas is a raster bitmap. Increasing its intrinsic pixel dimensions before drawing is the practical way to improve print sharpness; converting it to an image does not make it vector.

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

Can I use a custom readiness event instead of a global flag?

Yes. Wait for any page-side condition you control, such as a status value or a promise-backed state. A global boolean is merely the smallest interoperable example.

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.

Should I revoke an object URL before closing the page?

Revoke it after the PDF has been generated. Closing the page also releases its resources, but explicit revocation is preferable when the page or browser will be reused.

Why does a successful screenshot not prove PDF output will match?

PDF generation applies print-media rules and PDF-specific background and color behavior. A screenshot may use screen media, so test the actual page.pdf() path with the media and background settings your document requires.

Frequently Asked Questions

Does Puppeteer preserve a canvas as vector graphics in a PDF?

No. A canvas is a raster bitmap; use larger intrinsic dimensions for sharper output.

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

Can I use a custom readiness event instead of a global flag?

Yes. Wait for any page-side state you control; canvasReady is only a simple example.

Should I revoke an object URL before closing the page?

Revoke it after PDF generation if the page remains open; closing the page will also release its resources.

Why can a screenshot and PDF of the same page differ?

PDF generation uses print-media and PDF background settings, while a screenshot may use screen media.

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.

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.