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.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Render your template into a complete HTML document, load it in Chromium through Puppeteer or Playwright, and call page.pdf(). The example below uses Handlebars and Puppeteer to create an A4 PDF with print backgrounds and explicit margins. For a service, reuse a bounded browser pool rather than launching an untracked browser for every request.

Choose a renderer and understand the pipeline

Server-side PDF generation is a two-stage job: first turn application data into HTML, then ask a browser engine to print that document. Handlebars, EJS, or another template engine can perform the first stage; Puppeteer and Playwright provide Chromium-backed page and PDF APIs for the second.

  1. Validate the data that will populate the document.
  2. Render a complete HTML document, including styles and any required assets.
  3. Load it into a browser page and wait for the content the PDF depends on.
  4. Select print or screen media deliberately.
  5. Set page size, margins, colors, and any headers or footers, then save or return the PDF bytes.
  6. Close the page and browser, or return the page to a carefully managed browser pool.

Use Puppeteer if your project already uses its Chrome-focused API or you want a narrow integration. Choose Playwright if it is already part of your broader browser-automation or testing stack. Both approaches require you to manage browser processes, rendering time, memory, and asset availability.

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

Generate an A4 PDF with Handlebars and Puppeteer

Install Puppeteer and Handlebars in your Node.js project, and ensure the runtime can launch the compatible browser binary. Pin compatible package and browser versions in your lockfile; browser installation and deployment are part of the application, not an incidental detail.

This ES module example reads an HTML template, compiles it with sample data, waits for network activity to settle, and writes the PDF bytes to disk. It uses print media explicitly; Puppeteer PDF generation uses print CSS by default.

import puppeteer from 'puppeteer';
import Handlebars from 'handlebars';
import { readFile, writeFile } from 'node:fs/promises';

const template = await readFile('./invoice.html', 'utf8');
const html = Handlebars.compile(template)({
  invoiceNumber: 'INV-1001',
  customer: { name: 'Ada Lovelace' },
  lines: [{ description: 'Consulting', amount: '120.00' }]
});

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: 'networkidle0' });
  await page.emulateMediaType('print');
  const pdf = await page.pdf({
    path: './invoice.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '18mm', right: '14mm', bottom: '18mm', left: '14mm' }
  });
  await writeFile('./invoice.pdf', pdf);
} finally {
  await browser.close();
}

The path option writes a file directly; the returned value is also PDF data, as used above. If an HTTP handler needs to send the document rather than persist it, return those bytes with an appropriate PDF content type and a deliberate filename. Avoid writing to a shared fixed path in a concurrent service: requests can overwrite one another.

Template requirements and safe data

The template should be a complete document, with its stylesheet included or loaded from a location the browser can reach. For stylesheets and images, relative URLs may resolve differently in a deployment environment than on a developer’s machine. Use absolute or data URLs when relative paths are not dependable. Ensure fonts are available before printing; Puppeteer documents that page.pdf() waits for fonts by default.

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.

Treat data supplied by users as untrusted. Let the template engine escape ordinary values, and do not interpolate unsanitized HTML into a document that can execute scripts or request internal resources. If rich HTML is a product requirement, sanitize it using an explicit policy before rendering and restrict what the browser can reach.

Wait for the right content before printing

networkidle0 is useful when the page’s assets load through ordinary network requests, but it is not a universal readiness guarantee. A chart, client-side component, or asynchronous data request can finish after the network appears idle—or keep the network busy indefinitely. For a URL, wait for the relevant navigation state; Puppeteer’s guide demonstrates waitUntil: 'networkidle2'. For HTML strings, use an application-specific readiness signal for asynchronous work.

For example, when your template’s client code sets window.pdfReady = true only after charts and data are rendered, wait for that condition before generating the PDF:

await page.waitForFunction(() => window.pdfReady === true, { timeout: 15000 });
const pdf = await page.pdf({ format: 'A4', printBackground: true });

Define and set such a signal in your own template only if it accurately represents all required content. Add finite timeouts to readiness waits so a missing asset or broken script cannot hold a job forever. If the document is static and self-contained, no application readiness flag may be necessary.

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

Control print layout, color, and pagination

PDF output uses print media by default. That means print-specific CSS can differ from what you see in a browser window. Use @media print and @page rules for print layout, and page-break controls such as break-inside where supported by the browser. Set paper size and margins explicitly in the PDF options so the result does not silently depend on defaults.

  • Backgrounds: Set printBackground: true when backgrounds are part of the design.
  • Screen-designed templates: Use page.emulateMediaType('screen') before printing if the template was built for screen styles rather than print styles. Check the result carefully; screen media can change layout and pagination.
  • Exact color: Print output may adjust colors. The documented CSS technique is -webkit-print-color-adjust when exact colors are needed; verify the actual output in your browser version.
  • Headers and footers: Puppeteer’s PDF options support displayHeaderFooter, headerTemplate, and footerTemplate. Include these only when the document needs them and test their spacing against the page margins.

For a long document, check page breaks with representative data, not just a short sample. A table row, heading, or signature block can paginate differently when content length changes. Use visual regression fixtures in your own test suite to catch unexpected changes after template, font, or browser updates.

Use Playwright if it fits your stack

Playwright also provides page.pdf(), which returns a PDF buffer and uses print CSS by default. Its media selection API is page.emulateMedia({ media: 'screen' }); its PDF options accept width and height units such as px, in, cm, and mm, and paper formats include A4 and Letter. Color printing has the same documented caveat about print color adjustment.

The rendering sequence is otherwise the same: launch the browser, create a page, load or set its content, wait for application readiness, choose the media mode, call page.pdf(), and clean up. Pick the tool your application can operate and update reliably rather than expecting PDF layout to eliminate browser lifecycle work.

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

Deploy PDF generation reliably

  • Pin versions: Keep package and browser versions compatible in the lockfile and deploy the same combination you validate.
  • Plan browser installation: Cache browser downloads in CI where practical. The pdf-creator-node package documentation describes Puppeteer’s compatible Chromium download as hundreds of megabytes; it gives no precise measured figure, so do not budget from an assumed exact size.
  • Bound concurrency: Reuse a browser process carefully for throughput, isolate pages between jobs, and enforce timeouts. Close each page after its job even when rendering fails.
  • Set limits: Put limits on render duration and concurrent jobs. A slow remote asset or a page that never becomes ready can otherwise consume browser capacity.
  • Log safely: Record template, renderer, and browser errors without logging sensitive document contents.
  • Test representative outputs: Keep visual regression fixtures for short and long documents, different page counts, and relevant optional sections.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot common PDF failures

The PDF is blank or missing dynamic content

The page may have been printed before client-side rendering completed. Wait for a meaningful selector or application readiness flag before calling page.pdf(). For URL navigation, choose a navigation wait appropriate to the page rather than assuming the first response contains the finished document.

Images, stylesheets, or fonts are missing

Check whether the browser process can access the asset URLs in the deployed environment, and whether paths that worked locally are still valid. Use absolute or data URLs where relative paths are unreliable. Ensure fonts are installed or reachable; Puppeteer waits for fonts by default during PDF generation, but that cannot make a missing font available.

Colors or layout differ from the browser preview

PDF printing uses print CSS by default. Add or correct print styles, or explicitly emulate screen media if that is the intended design. Enable printBackground for background graphics and use -webkit-print-color-adjust where exact print colors are required. Recheck pagination after changing media mode.

A render hangs or times out

Look for a readiness condition that never becomes true, a network request that never finishes, or remote assets that are slow or inaccessible. Use finite navigation and readiness timeouts, log the failure category, and close the page in a finally path. Do not make every job wait indefinitely for global network idleness if the page continually polls.

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

PDF files overwrite one another or disappear

Concurrent jobs should not write to the same fixed filename. Return the bytes directly or write each job to a unique path and manage cleanup explicitly. In an API, handle failed rendering separately from successful responses so a partial or stale file cannot be mistaken for the current document.

Or skip the browser setup

If your input is an already-rendered, publicly reachable webpage and you need a capture rather than a server-side template renderer, ScreenshotNeo offers a screenshot API and MCP server. This is not a substitute for rendering your private Handlebars or EJS data into HTML. It can capture a webpage as an image or PDF; check the API documentation for the PDF request options. Here is the one-call screenshot example for a URL:

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

ScreenshotNeo removes cookie/consent banners, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, and failed loads are never billed, and response headers report page verdict and billing status. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month with no card.

Frequently Asked Questions

Can I generate a PDF from a local HTML file instead of a template string?

Yes. Load the file into the browser page with an appropriate file URL or read and set its HTML content; make sure its linked assets resolve from the rendering environment.

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

Does Playwright’s PDF output use screen styling by default?

No. Its PDF API uses print CSS by default; explicitly emulate screen media when that is the intended stylesheet.

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.