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 matchDirect answer: Compile the Handlebars source into a complete HTML string, give that string to a Puppeteer page, wait for the page’s fonts and image requests, then call page.pdf() with the media, background, and page-size options your design requires. CSS and images are not part of Handlebars compilation; Chromium must be able to resolve them when it renders the resulting HTML.
The complete pipeline
- Install Handlebars and Puppeteer.
- Compile the template with
Handlebars.compile()and render it with your data. - Create a Chromium page and load the rendered HTML.
- Wait for navigation, fonts, and images.
- Choose print or screen media, then generate the PDF.
Handlebars produces HTML; Puppeteer renders that HTML in Chromium. Keep the template a complete document (or a complete fragment with all required styles) so the browser has predictable input.
Install the dependencies
npm install handlebars puppeteer
Yarn users can run yarn add handlebars puppeteer. The CommonJS form used below follows Handlebars’ documented installation pattern.
Build a template that contains CSS and images
Use an inline <style> block for CSS that must travel with the HTML, or use a stylesheet URL that the Chromium process can reach. Image src values must also resolve inside the deployment environment.
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 →#1 Best Overall
const templateSource = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
@page { size: A4; margin: 18mm; }
body { font-family: Arial, sans-serif; color: #1f2937; }
.hero { background: #0f766e; color: white; padding: 24px; }
.hero img { width: 140px; height: auto; display: block; }
@media print {
.screen-only { display: none !important; }
}
</style>
</head>
<body>
<section class="hero">
<img src="{{logoUrl}}" alt="{{company}} logo">
<h1>{{title}}</h1>
</section>
<p>Prepared for {{customer}}.</p>
<ul>
{{#each items}}<li>{{this}}</li>{{/each}}
</ul>
</body>
</html>`;
Handlebars escapes normal interpolations, which is safer for user data. Use triple-stash expressions only for HTML you intentionally sanitize and trust. If you use a local image, a file:// URL or data URL can be appropriate, but validate the path and Chromium security settings in your target container. Absolute HTTPS URLs are usually simpler for a hosted asset; private URLs may require request interception or signed access.
Compile and render the HTML
const Handlebars = require('handlebars');
const template = Handlebars.compile(templateSource);
const html = template({
company: 'Example Co',
title: 'Quarterly report',
customer: 'Ada Lovelace',
logoUrl: 'https://example.com/assets/logo.png',
items: ['Revenue increased', 'Churn decreased']
});
Handlebars.compile() returns a render function. Calling it with a data object substitutes variables and expands blocks such as #each. For high-volume deployments, Handlebars also supports precompilation; pair a precompiled template with the same runtime version used to execute it.
Load the rendered page in Puppeteer
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
// PDF generation follows here.
} finally {
await browser.close();
}
Use networkidle0 when you need all network requests to become idle, or networkidle2 when a page legitimately keeps one or two connections open. The exact behavior can vary with the Puppeteer version, so verify it in your deployment.
Wait explicitly for images
Network-idle navigation is useful, but image readiness can still be deployment-specific. Before printing, wait until every image reports completion and a natural width.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →await page.waitForFunction(() => {
return Array.from(document.images).every(img => img.complete && img.naturalWidth > 0);
});
This rejects the wait when an image failed. If broken images are acceptable, test img.complete alone and log failed URLs instead. A selector-specific wait is useful for lazy-loaded content; trigger scrolling or use the page’s own loading behavior before the check.
Wait for fonts
Puppeteer’s PDF API waits for fonts by default through waitForFonts: true. You can also make readiness explicit:
await page.evaluate(async () => { await document.fonts.ready; });
Choose print or screen CSS
Puppeteer’s page.pdf() generates a PDF with the print CSS media type by default. Put print-only rules in @media print. If your design depends on screen media queries, switch before creating the PDF:
await page.emulateMediaType('screen');
Do not switch media casually: screen and print rules can change colors, visibility, dimensions, and pagination. Decide which stylesheet is authoritative for the document, then test both the browser view and PDF.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #3
Generate the PDF with reliable options
await page.pdf({
path: 'output.pdf',
printBackground: true,
preferCSSPageSize: true,
waitForFonts: true
});
printBackground is false by default, so colored sections, background images, and shaded table rows disappear unless you enable it. Use one of these sizing strategies:
| Strategy | When to use it | Important option |
|---|---|---|
| Named format | Standard paper such as A4 or Letter | format: 'A4' |
| Explicit dimensions | Labels, receipts, or a custom sheet | width and height |
| CSS page size | The stylesheet owns pagination | preferCSSPageSize: true |
When @page { size: ... } defines the layout, preferCSSPageSize: true lets CSS take precedence over a conflicting format or dimension. You can additionally set margin, landscape: true, scale, and pageRanges.
await page.pdf({
path: 'invoice.pdf',
format: 'A4',
landscape: false,
margin: { top: '18mm', right: '18mm', bottom: '18mm', left: '18mm' },
printBackground: true,
preferCSSPageSize: true,
pageRanges: '1-3',
scale: 1,
waitForFonts: true
});
Complete runnable example
const Handlebars = require('handlebars');
const puppeteer = require('puppeteer');
const source = `<!doctype html>
<html><head><meta charset="utf-8">
<style>
@page { size: A4; margin: 16mm; }
body { font-family: Arial, sans-serif; }
.banner { background: #2563eb; color: #fff; padding: 20px; }
.banner img { max-width: 120px; }
</style></head>
<body>
<div class="banner"><img src="{{logoUrl}}" alt="Logo"><h1>{{title}}</h1></div>
{{#each rows}}<p>{{this}}</p>{{/each}}
</body></html>`;
(async () => {
const template = Handlebars.compile(source);
const html = template({
logoUrl: 'https://example.com/logo.png',
title: 'Monthly statement',
rows: ['First item', 'Second item']
});
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.waitForFunction(() => Array.from(document.images)
.every(img => img.complete && img.naturalWidth > 0));
await page.evaluate(async () => { await document.fonts.ready; });
await page.pdf({
path: 'statement.pdf',
printBackground: true,
preferCSSPageSize: true,
waitForFonts: true
});
} finally {
await browser.close();
}
})();
CSS and image troubleshooting
CSS appears unstyled
- Check that external stylesheet URLs are reachable from the machine running Chromium.
- Inspect the generated HTML, not just the Handlebars source, for missing variables or malformed tags.
- Remember that PDF rendering uses print media unless you call
emulateMediaType('screen'). - For deterministic output, inline critical CSS or serve assets from a stable, deployment-correct URL.
Backgrounds are missing
Set printBackground: true. Also check whether the background is applied by a print rule that is overridden or hidden.
Images are blank or broken
- Confirm the final
srcis absolute or otherwise valid in the container. - Wait for completion and verify
naturalWidth. - Check TLS, authentication, hotlink protection, redirects, and the image response’s content type.
- Embed critical small images as data URLs when deployment access is unreliable; test the resulting HTML because large data URLs increase memory use.
Fonts fall back
Ensure the font files are reachable and that their CSS is loaded before printing. Keep waitForFonts: true and await document.fonts.ready; inspect the browser console and network responses for blocked font requests.
Free tools Windows power users keep installed
One-click scans. No signup required.
PDF pagination is wrong
Choose one sizing authority. If CSS defines @page, enable preferCSSPageSize; otherwise use format or explicit dimensions. Review margins, break-inside, break-before, and oversized images that force unexpected page breaks.
The page never becomes idle
Analytics, WebSockets, polling, and long-lived requests can prevent an idle condition. Use a realistic wait strategy, wait for a known selector, or disable nonessential requests with request interception. Always retain a timeout and produce diagnostics rather than waiting indefinitely.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Runtime, reliability, and cost considerations
Compile templates once when the source is unchanged, and reuse a browser process where your service model allows it; launching Chromium for every request adds startup work. Isolate untrusted template data, set request and navigation timeouts, and close pages and browsers in finally blocks. Record the generated HTML, failed asset URLs, media type, page size, and Puppeteer version when diagnosing differences between environments.
There are no universal performance numbers for this pipeline: rendering time depends on Chromium startup, template size, network latency, image dimensions, font loading, JavaScript, and PDF complexity. Cache immutable assets and avoid unnecessary full-page resources, but do not remove a request that your document needs.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server when you need a rendered page without maintaining Puppeteer infrastructure. It accepts a URL and returns PNG, JPEG, WebP, or PDF. Before capture it accepts the cookie or consent banner like a visitor 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 response headers report the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo documentation for all options, including full-page capture, CSS-selector elements, dark mode, device presets, retina scale, PDF paper and page ranges, custom CSS or JavaScript, clicks, waits, blocked resources, headers, cookies, user agents, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, async webhooks, bulk capture of up to 100 URLs per call, usage reporting, and the OpenAPI specification. Parameter names used by other screenshot APIs also work to ease migration.
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}`);
The Free plan includes 1,000 shots each month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is on every plan. Sign up for ScreenshotNeo to start with the free allowance.
FAQ
Can I use a Handlebars partial for the document head?
Yes. Register the partial before compilation, then include it in the template; ensure the expanded result still contains the styles and metadata the browser needs.
Should I use networkidle0 or networkidle2?
Use networkidle0 for a page expected to finish all requests. Choose networkidle2 when a small number of persistent connections are normal, then add explicit waits for the content that determines correctness.
Does page.pdf() print CSS backgrounds automatically?
No. Enable printBackground: true; its default is false.

