ScreenshotNeo

BlogHTML to image & PDF

How to Print JavaScript-Rendered Pages and Charts to PDF

Learn browser printing, print CSS, Chart.js resizing, and Puppeteer automation for reliable PDFs from JavaScript-rendered pages.

By the ScreenshotNeo team29 September 20268 min read

How to Print JavaScript-Rendered Pages and Charts to PDF

Short answer: For a one-off PDF, let the JavaScript finish rendering, open the browser print dialog, inspect the preview, and save as PDF. For repeatable output, use Puppeteer: navigate after the page is ready, apply print CSS, resize responsive charts when needed, and call page.pdf(). Always inspect the generated PDF because print layout, fonts, chart sizing, colors, and page breaks can differ from the screen.

This guide covers browser printing, CSS for pages you control, the canvas-chart edge case, a complete Puppeteer workflow, PDF options, troubleshooting, and a hosted alternative with ScreenshotNeo.

1. Print a rendered page from a browser

Browser printing is the fastest approach when a person needs to save a page once.

  1. Open the page and wait for data, charts, web fonts, and images to finish loading.
  2. Open the browser’s print interface (commonly Ctrl+P on Windows/Linux or Command+P on macOS).
  3. Choose a PDF or “Save to PDF” destination when your browser and operating system provide one.
  4. Choose paper size, portrait or landscape orientation, scale, and margins.
  5. Review every page in the preview. Check chart labels, clipped content, blank canvases, colors, and page breaks.
  6. Save the PDF, then open the file separately to verify the result.

A wide chart often needs landscape orientation or a larger paper size. A long dashboard may need a print-specific layout rather than simply shrinking the screen layout.

2. Add print CSS to a page you control

CSS can replace an interactive screen layout with a document layout. MDN describes the print media type as applying styles used for printed content (MDN printing guide).

A rendered page becomes a PDF after print styles and chart sizing are applied.
A rendered page becomes a PDF after print styles and chart sizing are applied.
/* screen layout remains the default */
@media print {
  nav,
  .toolbar,
  .sticky-controls,
  button,
  .chat-widget,
  .screen-only {
    display: none !important;
  }

  body {
    margin: 0;
    color: #111;
    background: #fff;
    font-size: 11pt;
  }

  .report {
    width: 100%;
  }

  .card,
  .chart {
    break-inside: avoid;
  }

  h1, h2, h3 {
    break-after: avoid;
  }
}

@page {
  size: A4 portrait;
  margin: 14mm 12mm;
}

@media print {
  .wide-chart {
    break-before: auto;
    break-after: auto;
  }
}

The @page rule sets page dimensions, orientation, and margins. Browser support for individual features can vary, so preview the output in the browsers your users rely on (MDN @page reference). Use print rules for structural changes such as hiding navigation, removing sticky controls, changing widths, and controlling page breaks.

Do not assume screen colors will be identical in the PDF. Print rendering can alter colors, and backgrounds may be omitted unless the browser’s print settings allow them. Keep important information in text or borders rather than relying only on a background fill.

3. Make responsive charts print correctly

A canvas chart can be visible and correct on screen but blank, clipped, or blurry in print preview. Canvas has an internal render size and a separate CSS display size; changing only CSS can leave the drawing buffer at the wrong dimensions.

Chart.js documents beforeprint and afterprint hooks for this case. Its guidance is to resize charts explicitly before printing, then restore automatic sizing afterward (Chart.js responsive charts documentation).

const chart = new Chart(document.getElementById('sales'), {
  type: 'line',
  data: salesData,
  options: {
    responsive: true,
    maintainAspectRatio: false
  }
});

window.addEventListener('beforeprint', () => {
  // Pick dimensions that fit your intended paper layout.
  chart.resize(1000, 520);
});

window.addEventListener('afterprint', () => {
  // Passing no dimensions returns the chart to responsive sizing.
  chart.resize();
});

Apply the same idea to other chart libraries using their documented resize or redraw API. The exact method is library-specific. Trigger the resize only after the chart’s data has arrived and the print layout has been applied. Browser print layout and resize-event timing can make automatic behavior unreliable, so inspect the preview and saved PDF.

4. Generate PDFs repeatedly with Puppeteer

Puppeteer is appropriate for scheduled reports, downloads, CI jobs, and any workflow that must be repeatable. Its Page.pdf() method generates a PDF using the print CSS media type by default (Puppeteer Page.pdf()).

Install and run a complete example

npm install puppeteer
const puppeteer = require('puppeteer');

const url = process.argv[2] || 'https://example.com/report';

(async () => {
  const browser = await puppeteer.launch({
    headless: true
  });

  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });

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

    // Replace this with an application-specific readiness condition.
    await page.waitForSelector('[data-report-ready]', { timeout: 30000 });

    // Let fonts finish before creating the document.
    await page.evaluate(() => document.fonts && document.fonts.ready);

    await page.pdf({
      path: 'report.pdf',
      format: 'A4',
      printBackground: true,
      margin: {
        top: '14mm',
        right: '12mm',
        bottom: '14mm',
        left: '12mm'
      },
      preferCSSPageSize: true
    });
  } finally {
    await browser.close();
  }
})();

Run it with node print-report.js https://your-site.example/report. The selector in the example is an application contract: render it only when the report’s data and charts are ready. If you cannot add such a marker, wait for a specific chart element, a known heading, or a bounded delay after the data request. There is no universal readiness signal for every JavaScript application.

Puppeteer uses print media for PDFs. If your page has a separate screen layout and you need that styling, call await page.emulateMediaType('screen') before page.pdf() (Puppeteer PDF generation guide). In most reports, print media is preferable because it activates your @media print rules.

5. Puppeteer PDF options that matter

Option Use it for Common caution
format Named paper sizes such as A4 or Letter Content still follows your page CSS and may span pages.
landscape Wide tables and charts Check that headings and margins remain readable.
width, height Custom paper dimensions Use consistently with your print CSS.
margin Printable whitespace around content Large margins can force unexpected page breaks.
printBackground Background colors and graphics It does not fix incorrect chart colors or missing assets.
pageRanges Export selected pages Validate the range against the final page count.
scale Fine adjustment of rendered size Scaling too far can make text hard to read.
preferCSSPageSize Let @page size take precedence Confirm that the CSS size matches your intended output.

The current Puppeteer API reference documents these and additional PDFOptions fields (PDFOptions interface). Set only options you need, then inspect the PDF. Puppeteer waits for fonts by default, but delayed data, authentication, animations, and continuing network activity still require application-specific handling.

6. Colors, backgrounds, and page breaks

Puppeteer notes that PDF colors are modified for printing by default. If exact colors are required, request print color adjustment in your stylesheet:

@media print {
  * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

Use this deliberately: exact colors can consume more ink on physical printers, and the final appearance still depends on the PDF viewer and printer. Keep page-break rules close to the components they govern:

.section {
  break-inside: avoid;
}

.section-title {
  break-after: avoid;
}

.new-page {
  break-before: page;
}

7. Troubleshooting checklist

Symptom Likely cause Fix
Chart is blank PDF captured before data or canvas drawing completed Wait for a readiness selector, verify chart data, then call the library’s resize or redraw method.
Chart is clipped Print width differs from screen width Use print CSS for the chart container and resize it in a print hook.
Text overlaps Screen-only absolute or sticky positioning survived printing Disable those rules in @media print and use normal document flow.
Backgrounds are missing Browser print settings or Puppeteer option excludes them Enable background graphics in the browser, or set printBackground: true.
Colors look different Print color adjustment changed them Use print-color-adjust: exact where needed and inspect the PDF viewer.
Fonts fall back Web fonts were not loaded at capture time Await document.fonts.ready and confirm the font request succeeds.
Content is stale Cached data, delayed API response, or an animation Use a deterministic data URL or readiness marker; disable animations for print.
Navigation times out Long polling, analytics, or streaming requests keep the page busy Use a targeted selector instead of relying only on networkidle2; block nonessential requests when appropriate.
Only part of a dashboard appears Viewport or page height assumptions Use full-page print CSS, remove fixed-height containers, and verify all sections in the PDF.

8. Performance, reliability, and cost

  • Readiness: A precise selector is usually more reliable than an arbitrary sleep. Still allow time for fonts and canvas redraw after the selector appears.
  • Assets: Large images and many web fonts increase capture time and PDF size. Serve appropriately sized assets and remove print-only decorations.
  • Animations: Pause transitions and animated charts in print CSS so the captured frame is deterministic.
  • Isolation: Launch a fresh page for each job, close it after capture, and close the browser in a finally block.
  • Retries: Retry transient navigation or asset failures with a limit, and log the URL, readiness condition, and resulting file for diagnosis.
  • Inspection: Keep a sample PDF in CI or review output after changes to print CSS, chart versions, fonts, or page content.

Browser automation has an infrastructure cost: Chromium installation, memory, process lifecycle, and maintenance of readiness logic. A hosted screenshot or PDF API can move those concerns out of your application.

Consent banners and overlays can be removed before a hosted capture.
Consent banners and overlays can be removed before a hosted capture.

Or skip the browser setup

ScreenshotNeo provides a website capture API and MCP server. Use the same simple GET request from cURL, Python, or Node.js; see the ScreenshotNeo API documentation for PDF and capture options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, and cache hits are never billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server lets AI agents such as Claude, Cursor, and other MCP clients take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

Create a free ScreenshotNeo account and start with the 1,000 included screenshots.

FAQ

Can a PDF preserve JavaScript interactivity?

No. The PDF is a document snapshot. Links may remain clickable depending on the generator, but live controls, animations, and application state are not preserved as interactive features.

Should I use the browser dialog or Puppeteer?

Use the dialog for an occasional manual save. Use Puppeteer when the same report must be generated repeatedly, on a schedule, or in CI.

Why does a chart look correct on screen but wrong in the PDF?

Print layout can change the canvas dimensions after the chart rendered. Add a print-time resize or redraw hook and verify the saved PDF at its actual paper size.

How do I choose portrait versus landscape?

Choose the orientation that keeps the widest content readable without excessive scaling. Wide charts and tables usually need landscape; text-heavy reports usually fit portrait.