PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchSome links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
For an existing HTML/CSS template, the most reliable Node.js workflow is to render your data into HTML, load that HTML in Puppeteer, wait until fonts and other required assets are ready, and call page.pdf(). Chromium performs the same layout work as a browser, so your CSS, web fonts, tables and responsive rules can be reused instead of rebuilt with PDF drawing commands.
The recommended workflow
- Compile the template with escaped data (Handlebars, EJS, Nunjucks or your own renderer).
- Launch a pinned Puppeteer/Chromium version.
- Open the rendered HTML with
page.setContent()or navigate to a controlled URL. - Wait for network resources, fonts and application-specific content.
- Choose print or screen media deliberately, then call
page.pdf(). - Close the page and reuse the browser process for the next job.
Puppeteer’s PDF API uses print CSS media by default. That is normally correct for invoices and reports, but templates designed only for screens may need page.emulateMediaType('screen'). Add -webkit-print-color-adjust: exact in print CSS when background colors must be preserved.
A complete Node.js example
Install and prepare a template
This example uses an HTML template function to keep the setup independent of a particular templating package. In production, replace the function with Handlebars, EJS or another engine that escapes user-controlled values.
npm install puppeteer
Create generate-pdf.mjs:
import puppeteer from 'puppeteer';
import { writeFile } from 'node:fs/promises';
function escapeHtml(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
function renderInvoice(data) {
const rows = data.items.map(item => `
<tr>
<td>${escapeHtml(item.description)}</td>
<td class="number">${item.quantity}</td>
<td class="number">${item.price.toFixed(2)}</td>
<td class="number">${(item.quantity * item.price).toFixed(2)}</td>
</tr>`).join('');
return `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
@page { size: A4; margin: 20mm 15mm; }
* { box-sizing: border-box; }
body { font: 12pt Arial, sans-serif; color: #222; margin: 0; }
h1 { color: #123b66; margin: 0 0 8px; }
.meta { color: #555; margin-bottom: 24px; }
table { width: 100%; border-collapse: collapse; }
th, td { border-bottom: 1px solid #ddd; padding: 8px 4px; text-align: left; }
th { background: #edf3f8; }
.number { text-align: right; }
.total { margin-top: 18px; text-align: right; font-size: 14pt; font-weight: bold; }
tr { break-inside: avoid; }
@media print { body { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }
</style>
</head>
<body>
<h1>Invoice ${escapeHtml(data.number)}</h1>
<div class="meta">${escapeHtml(data.customer)} · ${escapeHtml(data.date)}</div>
<table>
<thead><tr><th>Description</th><th class="number">Qty</th><th class="number">Price</th><th class="number">Amount</th></tr></thead>
<tbody>${rows}</tbody>
</table>
<div class="total">Total: ${data.total.toFixed(2)}</div>
</body>
</html>`;
}
const data = {
number: 'INV-1007',
customer: 'Acme Ltd',
date: '2026-09-29',
items: [
{ description: 'Design work', quantity: 2, price: 450 },
{ description: 'Hosting', quantity: 1, price: 80 }
],
total: 980
};
const html = renderInvoice(data);
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.evaluate(async () => {
if (document.fonts?.ready) await document.fonts.ready;
await Promise.all([...document.images].map(img => img.complete
? Promise.resolve()
: new Promise(resolve => { img.addEventListener('load', resolve); img.addEventListener('error', resolve); })));
});
await page.pdf({
path: 'invoice.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' }
});
} finally {
await browser.close();
}
Run it with node generate-pdf.mjs. The resulting invoice.pdf uses the template’s page rules, includes background colors, and waits for fonts and images before printing.
#1 Best Overall
Template engines and safe data
Handlebars or EJS
Compile the template before giving it to Puppeteer. Configure the engine’s normal escaping syntax for names, addresses and notes. Only allow an explicit, sanitized HTML field to bypass escaping. Never concatenate untrusted text into a <script>, style block or event-handler attribute.
Images, fonts and charts
Base64 or local assets avoid an extra network dependency. For remote assets, ensure the Chromium process can reach the host and wait for them explicitly. Client-rendered charts need an application readiness signal—for example, set window.pdfReady = true after the chart finishes, then wait with page.waitForFunction(() => window.pdfReady === true). Puppeteer waits for fonts during PDF generation, but images, charts and other asynchronous work still require your readiness strategy.
Print CSS that survives real data
- Define
@pagesize and margins, or pass equivalent PDF options. - Use
break-inside: avoidfor invoice rows, cards and signatures that must stay together. - Add explicit headers and footers when page numbers or legal text are required; test them with long documents.
- Use fixed units such as millimetres for page geometry and test long names, large tables and empty fields.
- Keep print-only rules in
@media print. Use screen media only when the design intentionally depends on screen styles.
PDF options such as landscape, displayHeaderFooter, headerTemplate, footerTemplate and pageRanges let the application control output without changing the source template. Keep page-break decisions in CSS where possible so browser previews and PDFs agree.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →When to choose another library
| Approach | Best fit | Trade-off |
|---|---|---|
| Puppeteer plus HTML/CSS | Invoices, reports, certificates and branded layouts already expressed as web templates | Requires Chromium and browser-process operations |
| PDFKit | Code-defined drawings, text and streams without browser layout | You must implement layout, wrapping and pagination with PDF primitives |
| Handlebars wrapper such as pdf-creator-node | Less integration code around HTML templates | Retains Chromium startup and deployment cost; its documentation lists Node.js 18 or newer |
Use PDFKit when the source is not HTML and exact control over drawing commands or streams matters. A wrapper can be convenient for a small team, but it does not remove the browser requirement.
Rank #2
Performance, reliability and deployment
Reuse the browser
Launching Chromium for every document adds avoidable startup work. Keep one browser process per worker, create a fresh page for each job, and always close the page in a finally block. Recycle the browser periodically if your workload exposes memory growth.
Pin and cache dependencies
Pin the Puppeteer version and its compatible Chromium revision in deployment. Cache the browser binary in CI rather than downloading it during every build. Run a smoke test that generates a PDF after deployment.
Isolate untrusted templates
HTML can execute JavaScript and request network resources. For user-supplied templates, use a separate worker or container, restrict outbound access, set job timeouts, cap document size, and avoid exposing secrets in environment variables reachable by page scripts. Do not allow arbitrary navigation when a job only needs local HTML.
Recommended Free Tools
Validate output
Test representative data: one-line and multi-line names, empty sections, maximum table lengths, unusual Unicode, missing images and slow fonts. Compare page count, file size and key text in automated checks, then visually inspect page breaks for every template revision.
Rank #3
Troubleshooting
The PDF is blank or missing late content
Cause: the page was printed before client rendering completed. Fix: use waitUntil: 'networkidle0', wait for a known selector or readiness flag, and await chart/image completion.
Background colors disappear
Cause: print backgrounds are disabled or the stylesheet lets the browser adjust colors. Fix: pass printBackground: true and add -webkit-print-color-adjust: exact to print CSS.
The layout differs from the browser preview
Cause: PDF output uses print media by default. Fix: inspect print styles, or call page.emulateMediaType('screen') when the template is intentionally screen-designed. Also confirm the same viewport and fonts are used.
Fonts or images are missing
Cause: inaccessible URLs, blocked requests, CORS or a race condition. Fix: use reachable HTTPS or embedded assets, log failed requests, wait for document.fonts.ready and image completion, and verify the deployed Chromium network policy.
Rank #4
Chromium fails to launch in a container
Cause: missing shared libraries, sandbox restrictions or an incompatible binary. Fix: use a base image supported by your pinned Puppeteer release, install required system dependencies, and follow your platform’s documented sandbox configuration rather than disabling security blindly.
Large documents time out
Cause: oversized HTML, slow external assets or expensive client-side scripts. Fix: embed or cache stable assets, remove unnecessary JavaScript, set an explicit job timeout, split very large reports, and reuse the browser process.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your source is already a publicly reachable page, ScreenshotNeo can render that URL to a PDF through one request. It is not a replacement for compiling a private template inside your Node process, but it avoids packaging and operating Chromium for URL-based captures.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
For PDF output, use the PDF option documented in the ScreenshotNeo API documentation and set the target URL to your deployed, authenticated-free template page. The service also supports full-page capture, custom CSS and JavaScript, waiting for selectors or network idle, viewport and device settings, PDF paper size, margins, orientation and page ranges.
Its cleanup step accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be disabled. 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. An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
cURL, Python and Node.js request examples
The same endpoint is useful when another service owns template rendering. Replace the example URL with your deployed page and add the documented PDF parameters.
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)
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 returned ${res.status}`);
Frequently Asked Questions
Can Puppeteer generate a PDF without writing an HTML file first?
Yes. Render the template to a string and pass it directly to page.setContent(); a temporary file is unnecessary.
Should I use print or screen media for an invoice?
Use print media unless the template was deliberately designed around screen-only rules. Select screen media only after checking its page breaks and colors in a PDF.
Is PDFKit compatible with an existing CSS template?
Not directly. PDFKit draws text and shapes through JavaScript, so an HTML/CSS template normally fits Puppeteer better.
How do I generate only selected pages?
Pass a page range such as pageRanges: '1-3' to page.pdf(), after testing the document’s pagination.
Quick Recap
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.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

