October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
HTML to PDF

HTML-to-PDF Libraries on npm: What to Choose

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

For a modern React, Vue, SSR, or JavaScript-rendered page, start with a browser engine: Puppeteer or Playwright. They use Chromium to lay out real HTML and CSS, run page JavaScript, load web fonts, and print the result. Choose PDFKit when the document is a fixed, programmatic layout rather than an existing web page. Use html-pdf-node when you want a small wrapper around Puppeteer without changing its browser-runtime requirements.

The short answer

Need Best starting point Why Main trade-off
Existing HTML with modern CSS, JavaScript, charts, or web fonts Puppeteer or Playwright A real browser performs layout and client-side rendering You must deploy a compatible browser binary and fonts
Simple HTML conversion behind a small API html-pdf-node Convenient options for format, margins, scale, and CSS page size It is still a Puppeteer/Chromium deployment
Invoices, certificates, or fixed reports described by code PDFKit Direct control over coordinates, text, fonts, structure, and streams You must build the layout in PDFKit rather than reuse arbitrary CSS
Strict paged-media rules beyond normal browser printing A dedicated paged-media engine, evaluated separately May implement specialized pagination features Verify npm integration and run compatibility tests before committing
Legacy PhantomJS or wkhtmltopdf integration Migrate when feasible Modern browser engines track current web-platform behavior more closely Engine changes require visual-regression testing

There is no useful universal speed ranking here: published comparisons do not provide a controlled benchmark that can be generalized. Measure your own templates, browser version, fonts, concurrency, and deployment target.

Choose the rendering model before the package

Browser-engine rendering

Puppeteer and Playwright drive a browser page. The browser executes JavaScript, calculates CSS layout, paints backgrounds and images, and then exposes a PDF operation. This is the natural model when your source is already a website. It also means failures can come from navigation, blocked resources, missing fonts, sandbox configuration, or a page that never reaches a stable state.

Programmatic PDF generation

PDFKit is a PDF document generation library for Node and the browser. Its drawing and streaming API lets your code place text, paths, images, and other objects directly. That is often easier for a fixed invoice or certificate than reproducing the same geometry in HTML. It is not a drop-in CSS renderer: a complex website must be re-authored in PDFKit’s document model.

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

Convenience wrappers

html-pdf-node wraps Puppeteer. Its format, margin, scale, and preferCSSPageSize options reduce glue code for straightforward conversions, but the underlying Chromium binary, startup behavior, fonts, and container concerns remain.

Puppeteer: a complete HTML-to-PDF pipeline

Install and render a URL

Install Puppeteer in the application that owns the conversion:

npm install puppeteer

The following script waits for navigation, selects screen or print media deliberately, waits for web fonts, and writes an A4 PDF. Replace the URL with a route your worker can authenticate and reach.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    // In a container, configure the sandbox only according to your security policy.
  });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com/report', {
      waitUntil: 'networkidle2',
      timeout: 90_000
    });

    // Omit this line to use the default print media styles.
    await page.emulateMediaType('screen');
    await page.evaluate(() => document.fonts.ready);

    await page.pdf({
      path: 'report.pdf',
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' },
      displayHeaderFooter: true,
      headerTemplate: '<span></span>',
      footerTemplate: '<div style="font-size:9px;width:100%;text-align:center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>'
    });
  } finally {
    await browser.close();
  }
})();

Puppeteer prints with the print CSS media type by default. Use emulateMediaType('screen') only when the PDF should follow screen styling. preferCSSPageSize lets an @page rule in the document take precedence over the API format. Header and footer templates are separate HTML fragments; keep them small and style them inline.

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

Supplying authentication and page state

Set cookies or extra headers before navigation when the page is private. For bearer authentication, configure an extra HTTP header for the page and remove it after the job if the browser is reused. For pages that render after a known action, wait for a selector that proves the content is ready rather than relying only on a fixed delay. A network-idle condition can still be misleading when analytics, streams, or polling keep connections open.

Playwright: the alternative browser driver

Playwright is another maintained browser-engine approach. Its API is useful when your organization already uses Playwright for end-to-end tests or needs one automation stack. PDF generation is provided by the Chromium implementation, so verify the browser choice used by your service.

npm install playwright
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({
      viewport: { width: 1440, height: 900 },
      deviceScaleFactor: 1
    });
    await page.goto('https://example.com/report', {
      waitUntil: 'networkidle',
      timeout: 90_000
    });
    await page.emulateMedia({ media: 'print' });
    await page.evaluate(() => document.fonts.ready);
    await page.pdf({
      path: 'report.pdf',
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' }
    });
  } finally {
    await browser.close();
  }
})();

Do not select between Puppeteer and Playwright on a claimed benchmark that does not exist. Select the API your team can operate, keep its browser version pinned, and compare generated fixtures whenever you change the engine.

html-pdf-node: less glue, the same browser dependency

For a small conversion endpoint, the wrapper can be concise:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npm install html-pdf-node

const html_to_pdf = require('html-pdf-node');
const fs = require('node:fs/promises');

(async () => {
  const file = { url: 'https://example.com/report' };
  const options = {
    format: 'A4',
    printBackground: true,
    preferCSSPageSize: true,
    margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' },
    scale: 1
  };
  const buffers = await html_to_pdf.generatePdf(file, options);
  await fs.writeFile('report.pdf', buffers);
})();

Use this when its option surface matches your needs. When you need fine-grained page lifecycle control, request interception, or custom waits, use Puppeteer directly instead of hiding the browser behind a wrapper.

PDFKit: when HTML is the wrong input

PDFKit is a better fit when every element has a known position and the output is a document, not a printout of a website. It avoids browser startup and can stream output as it is generated.

npm install pdfkit

const PDFDocument = require('pdfkit');
const fs = require('node:fs');

const doc = new PDFDocument({ size: 'A4', margin: 50 });
doc.pipe(fs.createWriteStream('invoice.pdf'));
doc.fontSize(22).text('Invoice 1042');
doc.moveDown();
doc.fontSize(11).text('Consulting services                         $1,200.00');
doc.text('Tax                                             $96.00');
doc.moveDown();
doc.fontSize(14).text('Total                                      $1,296.00');
doc.end();

Notice what this code does not do: it does not parse an existing stylesheet, flexbox layout, client-side chart, or web component. Reuse PDFKit for templates that are intentionally expressed as drawing and text operations; do not choose it merely because a browser binary is inconvenient.

Print CSS and pagination controls that decide quality

Media, colors, and backgrounds

Print media can hide navigation, change typography, or remove screen-only controls. Define an explicit print stylesheet and inspect the output. If brand colors or chart fills disappear, enable background printing and use print color-adjust rules where appropriate. Screen emulation is an alternative, not a universal fix.

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

Page size and margins

Use @page when the document owns its paper geometry:

@page { size: A4; margin: 18mm 14mm; }
@media print {
  .no-print { display: none !important; }
  .avoid-split { break-inside: avoid; }
  h2 { break-after: avoid; }
}

Keep one source of truth. If the API’s format and margins conflict with CSS, choose deliberately; in Puppeteer-style APIs, preferCSSPageSize is the switch that honors the document’s declared size.

Headers, footers, and page breaks

Browser PDF APIs support page numbers, header/footer templates, margins, scaling, landscape output, and page ranges. Test long tables, repeated headings, orphaned titles, and images crossing a page boundary. A visually correct first page does not prove that later pages paginate correctly.

Fonts and external resources

Wait for document.fonts.ready before printing and make sure the production image contains the required font files. External CSS, images, and scripts must be reachable from the worker. A missing font can change line wrapping enough to move totals or signatures onto another page.

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.

Deployment, reliability, and cost decisions

Browser workers

Launching Chromium for every request is simple but adds startup work. A long-lived worker can reuse a browser while creating an isolated page or context per job. Cap concurrency so several large pages do not exhaust memory. Recycle the browser after a bounded number of jobs or when it shows leaks, crashes, or stalled pages.

Containers and serverless

Ship a browser binary compatible with the library version and include fonts in the image. Confirm executable paths, shared-memory limits, sandbox permissions, and temporary-file access. Serverless environments add cold-start and package-size constraints; test the actual runtime rather than assuming a local installation will behave the same way.

Timeouts and observability

Use separate budgets for navigation, resource loading, rendering, and PDF writing. Record the target URL, engine version, page verdict, elapsed time, and failure category without logging credentials or document contents. Capture a diagnostic screenshot or HTML snapshot only under an approved data-retention policy.

Where the money goes

The npm packages themselves are only one part of operating cost. Browser memory, CPU, container storage, font licensing, queue capacity, and engineering time for fixture tests matter more than an unsupported claim that one library is universally faster. PDFKit can reduce browser infrastructure for fixed layouts; it cannot remove the authoring cost of rebuilding a web page.

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

Troubleshooting common failures

  • PDF uses the wrong colors or layout: Check whether print media is active, then compare the print stylesheet with the screen stylesheet. Enable background printing and use screen emulation only when that is the intended design.
  • Content is missing: Wait for a definitive selector, a known application-ready signal, or completed fonts. A short sleep is a last resort because asynchronous charts and images may finish at different times.
  • Fonts fall back or text wraps differently: Install the fonts in the runtime, verify that font requests succeed, and await document.fonts.ready before calling pdf.
  • Navigation times out: Check DNS, TLS, authentication, robots or bot checks, and resources that never settle. Use a realistic timeout and a readiness selector instead of raising the limit indefinitely.
  • Browser fails to launch in a container: Verify the executable, shared memory, sandbox policy, and required system libraries. Keep the browser and automation package versions compatible.
  • Tables split badly: Add print-specific break rules, reduce oversized rows, and test with realistic data volumes. CSS cannot always keep a row together when it is taller than a page.
  • Only the first page is correct: Test page ranges, repeated headers, footer space, and long-running layout cases. Pagination defects often appear only after several pages.
  • Legacy output changed after migration: Expect differences between PhantomJS, wkhtmltopdf, and Chromium. Build fixture PDFs, compare rendered images, and review intentional changes instead of seeking pixel identity automatically.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a hosted screenshot and PDF API to try first when you want Chromium-style capture without operating browser workers. It accepts a URL in one GET request and can return PNG, JPEG, WebP, or PDF. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

For PDF work, configure paper size, margins, landscape mode, and page ranges. Other controls include custom CSS and JavaScript, selector waits or network-idle waits, custom headers, cookies, user agents and Authorization, timezone and geolocation, request or resource blocking, caching with a chosen TTL, asynchronous jobs with signed webhooks, and bulk capture of up to 100 URLs per call. The same service also offers element capture, full-page lazy-image loading, dark mode, device presets, retina scale, transparent backgrounds, resizing, signed links, a usage API, and an OpenAPI specification.

Use the ScreenshotNeo API documentation for authentication and option names. A direct call looks like this:

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)
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 body = await res.arrayBuffer();
require('node:fs').writeFileSync('shot.webp', Buffer.from(body));

An MCP server supplies take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; annual billing gives two months free, and every feature is on every plan. Create a free ScreenshotNeo account to start without a card.

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

How to test your choice before production

  1. Assemble representative fixtures: web fonts, long tables, charts, images, right-to-left text if applicable, and at least one multi-page document.
  2. Render each fixture in the exact container or serverless runtime you will deploy.
  3. Compare page count, text extraction, image presence, margins, headers, footers, and rasterized page images.
  4. Repeat after browser, package, font, or CSS upgrades. Treat engine changes as rendering changes, not ordinary dependency bumps.
  5. Load-test at your intended concurrency and record memory, timeout, and failure behavior instead of relying on anecdotal performance.

FAQ

Can one application use both PDFKit and Puppeteer?

Yes. Route fixed, code-defined documents to PDFKit and web-page documents to a browser worker. Keep the pipelines and regression fixtures separate so a change in one renderer does not silently alter the other.

Is html-pdf-node a different rendering engine?

No. It is a convenience wrapper around Puppeteer, so its output and operational requirements remain tied to the Puppeteer-controlled browser.

Should I promise identical PDFs across operating systems?

No. Browser version, installed fonts, system libraries, and available resources can change line breaks and pagination. Pin the runtime and render in a controlled environment.

When is a dedicated paged-media engine warranted?

Consider one when your requirements depend on specialized paged-media behavior that browser PDF controls cannot satisfy. Verify the npm package, integration path, and output against your own fixtures before adopting it.

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

Frequently Asked Questions

Can one application use both PDFKit and Puppeteer?

Yes. Route fixed, code-defined documents to PDFKit and web-page documents to a browser worker, with separate regression fixtures.

Is html-pdf-node a different rendering engine?

No. It wraps Puppeteer and retains Puppeteer’s browser and deployment requirements.

Should PDFs be expected to match across operating systems?

No. Pin the browser, fonts, and runtime because these can change line wrapping and pagination.

When should a dedicated paged-media engine be evaluated?

When specialized paged-media behavior exceeds what browser PDF controls provide; verify npm integration and your own fixtures first.

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.

Leave a Reply

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

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

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver 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.