ScreenshotNeo

BlogGuides

HTML to PDF Converter JavaScript Library

Compare html2pdf.js, Puppeteer and Playwright, with runnable JavaScript code, deployment guidance, troubleshooting and a simpler API option.

By the ScreenshotNeo team1 October 20269 min read

For an HTML to PDF converter JavaScript library, choose based on where rendering runs and whether the PDF must preserve real text and print CSS.

  • Use html2pdf.js when conversion can happen in a browser and an image-based PDF is acceptable.
  • Use Puppeteer when you need server-side Chrome rendering, selectable text and print-media CSS.
  • Evaluate Playwright when you already use its browser automation stack and can manage browser binaries and operating-system dependencies.

This guide gives complete examples, explains the rendering trade-offs, and shows a hosted option when you do not want to deploy a browser.

1. Decide which JavaScript approach fits

Requirement Best starting point Reason
Run entirely in a user’s browser html2pdf.js Client-side library built on html2canvas and jsPDF.
Run on a Node.js server Puppeteer Automates Chrome and exposes page.pdf().
Already operate Playwright Playwright Reuses your existing browser automation and deployment model.
Need searchable, selectable text Puppeteer or another browser print pipeline html2pdf.js places rendered content into the PDF as an image.
Need to avoid browser installation ScreenshotNeo A hosted API returns PDFs from one GET request and handles browser execution for you.

2. Browser-side conversion with html2pdf.js

html2pdf.js converts a webpage or element in the browser. Its documented pipeline is .from() -> .toContainer() -> .toCanvas() -> .toImg() -> .toPdf() -> .save(). The project explicitly states that it does not run in Node.js; it must run in a browser.

Install and load the library

npm install html2pdf.js

With a bundler:

import html2pdf from 'html2pdf.js';

const element = document.querySelector('#invoice');

await html2pdf()
  .set({
    margin: 12,
    filename: 'invoice.pdf',
    image: { type: 'jpeg', quality: 0.95 },
    html2canvas: {
      scale: 2,
      useCORS: true,
      backgroundColor: '#ffffff'
    },
    jsPDF: {
      unit: 'mm',
      format: 'a4',
      orientation: 'portrait'
    },
    pagebreak: {
      mode: ['css', 'legacy']
    }
  })
  .from(element)
  .save();

Or load the browser bundle from your own asset pipeline and call the same API from a script tag:

<button id="download">Download PDF</button>
<section id="invoice">
  <h1>Invoice 1042</h1>
  <p>Amount due: $240.00</p>
</section>

<script src="/assets/html2pdf.bundle.min.js"></script>
<script>
  document.querySelector('#download').addEventListener('click', async () => {
    await html2pdf()
      .set({ filename: 'invoice.pdf' })
      .from(document.querySelector('#invoice'))
      .save();
  });
</script>

Useful html2pdf.js options

Option What it controls Practical note
margin PDF margins Use a number or an array for per-side margins.
filename Downloaded file name Include a stable extension such as .pdf.
image.type Intermediate image format JPEG is smaller; PNG preserves sharp edges and transparency better.
image.quality JPEG quality Higher values improve quality and increase file size.
html2canvas.scale Raster resolution Increase for sharper output, but watch memory use.
html2canvas.useCORS Cross-origin image loading The image server must send compatible CORS headers.
jsPDF.format Paper size Common values include a4 and letter.
jsPDF.orientation Portrait or landscape Use landscape for wide tables.
pagebreak.mode Page-break handling CSS page-break rules are usually the most maintainable choice.

CSS for predictable page breaks

.pdf-page-break {
  break-before: page;
  page-break-before: always;
}

.avoid-split {
  break-inside: avoid;
  page-break-inside: avoid;
}

html2pdf.js limitations

The output is image-based, so text is not selectable or searchable and files can be large. The project also documents imperfect html2canvas rendering, cloning problems, root-element resizing that can trigger reflow, and browser canvas dimension limits that may produce blank output for very large documents. Test long pages, custom fonts, SVGs, charts and cross-origin images in the browsers you support.

3. Server-side conversion with Puppeteer

Puppeteer automates Chrome and supports PDF generation. Its page.pdf() method uses print CSS media by default. If the page is designed for screen media, call page.emulateMediaType('screen') before generating the file. Print output can also alter colors; use -webkit-print-color-adjust: exact when exact colors are required.

Install

npm install puppeteer

Complete Node.js example

import puppeteer from 'puppeteer';

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

  await page.emulateMediaType('print');
  await page.addStyleTag({
    content: `
      @page { size: A4; margin: 14mm; }
      * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
      .avoid-split { break-inside: avoid; page-break-inside: avoid; }
    `
  });

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

Rendering controls that matter

  • waitUntil: choose networkidle0 for pages that finish loading, or wait for a specific selector when analytics and long polling prevent network idle.
  • page.emulateMediaType(): select print or screen deliberately.
  • printBackground: include background colors and images.
  • preferCSSPageSize: let an @page rule control the paper size.
  • format, width, height and margin: define the printable geometry.
  • page.setViewport(): controls responsive breakpoints before layout is calculated.
  • page.goto() timeout: set a limit appropriate for your page and fail clearly when it is exceeded.

Waiting for application data

await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready]', { timeout: 30000 });
await page.evaluate(() => document.fonts.ready);
await page.pdf({ path: 'dashboard.pdf', printBackground: true });

4. Playwright as an alternative browser pipeline

Playwright is another browser automation option. Its documentation covers installing browser binaries, operating-system dependencies and browser cache management. Those requirements affect container images, cold starts, upgrades and deployment permissions, so include them in your evaluation.

npm install playwright
import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
  await page.goto('https://example.com/report', { waitUntil: 'networkidle' });
  await page.emulateMedia({ media: 'print' });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    preferCSSPageSize: true
  });
} finally {
  await browser.close();
}

Use the Playwright browser documentation for the installation command that matches your operating system and deployment image.

5. HTML and CSS patterns that survive PDF conversion

  • Define a paper size and margins with @page.
  • Use web fonts that are available in the target runtime, and wait for document.fonts.ready.
  • Give images explicit dimensions to reduce layout shifts.
  • Keep tables from splitting with break-inside: avoid on rows or grouped sections where supported.
  • Use print-specific rules under @media print and verify whether your tool uses print or screen media.
  • Set printBackground: true when colored panels or chart backgrounds are part of the document.
  • Avoid relying on a huge single canvas for very long documents.

6. Or skip the browser setup

ScreenshotNeo provides a hosted website capture API that can return a PDF. The API base is https://api.screenshotneo.com/v1/shot; see the API documentation for the complete option list.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

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(`ScreenshotNeo request failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

For PDF output, pass the PDF options described in the ScreenshotNeo docs. Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether the request was billed. An MCP server lets AI agents use take_screenshot, get_page_info and capture_pdf. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account.

7. Troubleshooting

Symptom Likely cause Fix
“html2pdf.js will not run in Node.js” The library requires a browser. Run it in browser code, or use Puppeteer, Playwright or ScreenshotNeo for server-side work.
PDF is blank or partially blank Canvas size limits, a failed resource, or capture before rendering completes. Split very large documents, wait for fonts and images, and inspect browser console errors.
Text is blurry or not selectable html2pdf.js rasterizes the page. Use a browser print pipeline such as Puppeteer when selectable text matters; increase canvas scale only for sharpness.
Images are missing Cross-origin restrictions or images not loaded before capture. Enable CORS where appropriate, use useCORS, preload images and wait for them.
Colors differ from the page Print color adjustment or print media styles. Set printBackground: true and -webkit-print-color-adjust: exact; verify print versus screen media.
Layout changes between runs Responsive viewport, late data, fonts or images changing dimensions. Set a fixed viewport, wait for a ready selector and await document.fonts.ready.
Page breaks split cards or rows Missing break rules or unsupported layout structure. Apply break-inside: avoid, group content, and test representative page lengths.
Puppeteer or Playwright fails in production Browser binaries or OS libraries are absent. Install the documented browser and system dependencies in the image, cache them, and pin compatible versions.
Navigation times out Slow resources, blocked requests or pages that never become idle. Wait for a specific selector instead of network idle, increase the timeout, and log the failing URL.

8. Performance, reliability and cost

Performance

  • Reuse a browser process for batches instead of launching one process per document.
  • Reuse pages carefully and clear cookies, storage and request interception between tenants.
  • Use a fixed viewport and avoid unnecessary resources such as analytics during rendering.
  • For html2pdf.js, lower canvas scale or split long documents when memory usage becomes a problem.
  • For hosted capture, use caching and a suitable wait strategy when the same URL is requested repeatedly.

Reliability

  • Log the URL, viewport, browser/library version and wait condition for every failed conversion.
  • Make output deterministic: pin dependencies, provide fonts, set timezone and wait for application readiness.
  • Test pages with long tables, lazy images, SVGs, custom fonts, dark backgrounds and responsive breakpoints.
  • Retry only transient navigation or infrastructure failures; do not hide invalid HTML or authentication errors with blind retries.

Cost and deployment

html2pdf.js has no server browser deployment cost, but it consumes the user’s CPU and memory and creates image-based PDFs. Puppeteer and Playwright add browser downloads, operating-system dependencies, memory use and maintenance to your infrastructure. A hosted API exchanges that setup for per-request pricing. ScreenshotNeo includes 1,000 shots per month free with no card; paid plans are $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000 and $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan.

9. Selection checklist

  • Does conversion need to run in the browser or on a server?
  • Must PDF text be searchable and selectable?
  • Do you need print CSS, screen CSS or both?
  • How will browser binaries and OS dependencies be installed and cached?
  • What happens when fonts, images, API data or third-party scripts are slow?
  • How will you handle page breaks, long documents and large tables?
  • What is your acceptable memory use, file size and conversion time?
  • Would a hosted API reduce operational work for your team?

10. FAQ

Can html2pdf.js run in a Node.js backend?

No. Its project documentation says it must run in a browser. Use Puppeteer, Playwright or a hosted service for Node.js server-side conversion.

Which option creates selectable PDF text?

A browser print pipeline such as Puppeteer generally preserves rendered text, while html2pdf.js documents image-based output with nonselectable, unsearchable text.

Why does Puppeteer use different styles than my page?

page.pdf() uses print media by default. Choose print or screen media explicitly and maintain matching CSS rules.

Is Playwright a drop-in replacement for Puppeteer PDF generation?

It is a candidate browser automation option, but evaluate its API, browser setup and deployment requirements against your application rather than assuming identical behavior.

When should I use ScreenshotNeo?

Use it when you want PDF or screenshot capture without packaging and operating a browser yourself, especially when consent banners, popups, failed loads and AI-agent access matter.