Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →The reliable way to convert a URL to a PDF in Node.js is to render it in a headless Chromium browser, wait for the page to reach a known ready state, call the browser’s PDF method, and save or stream the returned bytes. Puppeteer and Playwright both support this workflow. The important engineering decisions are not the single PDF call, but readiness detection, print styling, page geometry, timeouts, URL security and browser cleanup.
Choose a browser-based PDF workflow
A URL is usually an application, not a static file. It may need JavaScript, web fonts, client-side data fetching, cookies or responsive layout before its content is complete. A headless browser executes that work and produces the same kind of print rendering a user would get from a browser.
| Library | PDF result | Best fit | Important behavior |
|---|---|---|---|
| Puppeteer | Writes to a path or returns a buffer | Teams already using the Chrome-focused API | Page.pdf() uses print CSS media by default |
| Playwright | Returns a PDF buffer | Projects that also need Playwright’s browser automation APIs | page.pdf() supports print options and media emulation |
There is no universal speed or fidelity winner. Results vary with the browser version, page content and hosting environment, so benchmark your own workload rather than assuming one library is faster.
Convert a URL with Puppeteer
Install and run
- Create a project and install Puppeteer:
npm install puppeteer. The package provisions a compatible browser unless your deployment deliberately supplies one. - Save this as
url-to-pdf.mjs:
import puppeteer from 'puppeteer';
export async function urlToPdf(url, outputPath) {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60_000,
});
await page.pdf({
path: outputPath,
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
timeout: 30_000,
});
} finally {
await browser.close();
}
}
await urlToPdf('https://example.com', './example.pdf');
networkidle2 waits until there are no more than two active network connections for a short period. It is a useful starting point, not a guarantee that an application is ready. A page with analytics, polling or a stream can remain busy indefinitely.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minute#1 Best Overall
Return PDF bytes instead of writing a file
Omit path and Puppeteer returns a buffer. This is useful for an HTTP endpoint, object storage upload or a queue worker:
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
});
// await storage.put('reports/example.pdf', pdf);
return pdf;
Wait for an application-specific signal
If your page renders a report after an API call, wait for a selector that only appears when the report is complete:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForSelector('[data-pdf-ready="true"]', {
visible: true,
timeout: 30_000,
});
const pdf = await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
});
This is generally more predictable than waiting for global network idleness. You can also wait for a known delay when no selector exists, but a page-owned readiness marker is easier to test and maintain.
Convert a URL with Playwright
Install the library with npm install playwright. The following function navigates after the DOM is available and returns PDF bytes:
import { chromium } from 'playwright';
export async function urlToPdfBuffer(url) {
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.goto(url, {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await page.waitForLoadState('networkidle', { timeout: 30_000 }).catch(() => {});
return await page.pdf({
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
timeout: 30_000,
});
} finally {
await browser.close();
}
}
const pdf = await urlToPdfBuffer('https://example.com');
await import('node:fs/promises').then(fs => fs.writeFile('./example.pdf', pdf));
Playwright’s page.pdf() returns a buffer. Its PDF rendering uses print media by default; call await page.emulateMedia({ media: 'screen' }) before generating the PDF when the screen stylesheet is the intended design.
Rank #2
Control PDF layout and appearance
Paper size, dimensions and orientation
Use format: 'A4', 'Letter' or another supported paper format, or provide explicit width and height values such as '210mm' and '297mm'. Set landscape: true for wide tables. margin accepts top, right, bottom and left values with CSS units. preferCSSPageSize: true lets the page’s @page rule define the geometry instead of scaling it into the requested format.
Backgrounds, colors and scaling
Set printBackground: true when colored panels, images or background graphics belong in the document. PDF print rendering can alter colors. For exact brand colors, add -webkit-print-color-adjust: exact; in print CSS and verify the output in your target viewers. The scale option ranges from 0.1 to 2; lowering it can fit dense content, but it also reduces text size.
Print-specific CSS
@page { size: A4; margin: 16mm; }
@media print {
.screen-only { display: none !important; }
.report { -webkit-print-color-adjust: exact; }
h2, h3 { break-after: avoid; }
table, img { break-inside: avoid; }
}
Use print CSS to remove navigation, avoid splitting headings from their content and define page breaks. If the site was designed only for screens, emulate screen media before calling pdf(), then still test pagination because screen layouts can be wider than paper.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Headers and footers
Set displayHeaderFooter: true and provide headerTemplate or footerTemplate. Templates can include injected classes for the document date, title, URL, page number and total pages. Keep templates self-contained: external stylesheets and page scripts are not a dependable way to style them.
Serve a PDF from an Express endpoint
Never pass an unchecked user-supplied URL directly to a browser on a server. Validate the scheme, restrict hosts when possible and block access to private network ranges. Then stream the bytes with a PDF content type:
Rank #3
import express from 'express';
import puppeteer from 'puppeteer';
const app = express();
const browser = await puppeteer.launch();
app.get('/pdf', async (req, res) => {
const target = String(req.query.url || '');
let parsed;
try { parsed = new URL(target); } catch {
return res.status(400).send('Invalid URL');
}
if (!['http:', 'https:'].includes(parsed.protocol)) {
return res.status(400).send('Only HTTP and HTTPS URLs are allowed');
}
const page = await browser.newPage();
try {
await page.goto(parsed.href, { waitUntil: 'domcontentloaded', timeout: 60_000 });
const pdf = await page.pdf({ format: 'A4', printBackground: true });
res.type('application/pdf').send(pdf);
} catch (error) {
res.status(502).send('The page could not be rendered');
} finally {
await page.close();
}
});
app.listen(3000);
For production, add DNS/IP checks or an allowlist, request-size and concurrency limits, authentication, logging and a maximum PDF size. A long-lived browser can be reused, but create a fresh page per job and close it in finally. Close the browser during graceful shutdown.
Reliability, performance and cost decisions
Timeouts and retries
Set navigation and PDF timeouts explicitly. Retry only transient failures, and avoid retrying an invalid URL or a deterministic JavaScript error. A timeout should produce a controlled error rather than leave a browser or page running.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Fonts and images
PDF generation waits for fonts by default in Puppeteer’s PDF options. Self-hosted fonts and images must be reachable from the rendering environment; otherwise the PDF may contain fallback fonts or blank areas. For authenticated assets, configure cookies or headers before navigation.
Concurrency
Launching a browser for every request is simple but expensive. Reuse a browser and cap the number of simultaneous pages. Measure memory and CPU under your real document mix. Large, full-page captures and image-heavy pages require more resources than short text pages.
Output handling
Treat generated bytes as untrusted input until stored or streamed safely. Use a controlled filename, enforce a size limit, set Content-Disposition deliberately and scan or validate files if they enter a broader document workflow.
Rank #4
Common failures and fixes
- Navigation timeout: the page is slow, blocked or never becomes idle. Increase the timeout modestly, switch to
domcontentloadedand wait for a specific ready selector. - Blank or incomplete PDF: rendering finished before client-side content. Wait for the report selector, a known API response or an application-ready flag.
- Missing colors: enable
printBackgroundand add-webkit-print-color-adjust: exactto print CSS. - Wrong layout or unexpected pagination: inspect
@page, margins, width,preferCSSPageSizeand print-only break rules. - Fonts are wrong: ensure font URLs are reachable and wait for font loading before capture.
- Browser fails in a container: install the browser dependencies required by your image, use a compatible browser build and avoid unsafe sandbox disabling unless your deployment’s security model explicitly requires it.
- Server becomes unstable: cap concurrency, close every page, reuse a controlled browser pool and reject excessively large or numerous jobs.
Or skip the browser setup
ScreenshotNeo provides a URL-to-PDF API when you do not want to package and operate Chromium. One GET request returns a PDF (or PNG, JPEG or WebP), while its capture workflow accepts cookie banners and removes more than 60 known consent platforms, newsletter popups and chat widgets before the shot. Each step can be disabled.
Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
See the complete parameter list in the ScreenshotNeo documentation. This cURL request asks for a PDF:
curl -G "https://api.screenshotneo.com/v1/shot"
-d access_key=YOUR_API_KEY
--data-urlencode url=https://stripe.com
-d format=pdf
-o stripe.pdf
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "format": "pdf"},
timeout=90,
)
r.raise_for_status()
open("stripe.pdf", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com',
format: 'pdf'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo failed: ${res.status}`);
await Bun.write('stripe.pdf', res);
ScreenshotNeo also supports paper size, margins, landscape mode, page ranges, custom CSS and JavaScript, selectors, waits, headers, cookies, authorization, timezone, geolocation, caching with a chosen TTL, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call and a usage API. Every feature is on every plan: 1,000 shots per month are free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Can Node.js convert a URL without a browser?
Only when the source is already a simple, static document and you do not need browser layout. For modern JavaScript sites, a headless browser or a rendering API is the dependable approach.
Should I use a selector or network idle?
Prefer a selector or application-owned ready signal when you control the page. Network idle is convenient for ordinary documents but unreliable for pages with polling, streaming or persistent connections.
Can I generate a PDF from a protected page?
Yes, when your rendering context is authorized. Supply the required cookies or headers, and ensure every protected font, image and API request can be reached by the browser.
Frequently Asked Questions
Can Node.js convert a URL without a browser?
Only for simple static documents. JavaScript-heavy pages generally require a headless browser or a rendering API.
Should I use a selector or network idle?
Use an application-owned ready selector when available; network idle can hang on polling or streaming pages.
Can I generate a PDF from a protected page?
Yes, if the browser receives the required authorization, cookies and access to protected assets.
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.

