HTML to PDF in Node.js without Puppeteer¶
If your Node application already turns records into HTML, you can pass that HTML to Fullbleed to generate a PDF. The example below keeps the template, stylesheet, two font files and logo together, with a short order and a longer order to check page breaks. The main project installs Fullbleed 0.9.0, which contains engine 2.5.46; rendering does not launch a browser.
Use this path for static documents such as invoices, order summaries, statements
and reports. Keep Puppeteer when the required output depends on JavaScript running
in the page, a live application's state, or Chromium's rendering behavior.
Fullbleed is a separate layout engine, so migrating a template needs a layout
review. It is not a drop-in replacement for page.pdf().

Run the project¶
Use Node.js 22.12 or newer. Unzip the download, enter node-html-to-pdf, and run:
Open output/fullbleed/short/document.pdf and
output/fullbleed/long/document.pdf. Each folder also contains the exact HTML
input and native PNG previews. output/fullbleed/render.json records engine
version, page counts, missing-glyph counts and output hashes.
| File | What to change |
|---|---|
records.json |
Customers and line items; prices use integer cents. |
template.html |
The document structure. |
print.css |
Typography, page size, margins, tables and page numbers. |
document.mjs |
Your data-to-HTML step. This loader escapes text and computes totals. |
fonts/ and assets/ |
The supplied regular/bold fonts and SVG logo. |
render.mjs |
The Fullbleed call and file output. |
reference/ |
An optional, separate Puppeteer comparison. |
Render just the longer fixture, optionally into a directory with spaces:
Keep your data-to-HTML step¶
You can keep an existing template function or server-side template engine. Resolve the data and return HTML before calling Fullbleed. If a chart or other element is currently created by page JavaScript, generate an image or SVG first and supply it with the other assets.
Save this as example.mjs in the downloaded project, then run node example.mjs:
import { writeFile } from 'node:fs/promises';
import { renderPdf } from 'fullbleed';
import { loadDocument } from './document.mjs';
const input = await loadDocument('long');
const result = await renderPdf({
html: input.html,
fonts: input.fonts,
assets: input.assets,
defaultStyles: false,
previewDpi: 96,
isolation: 'process',
});
await writeFile('order.pdf', result.pdf);
console.log(`${result.pages} pages; engine ${result.engineVersion}`);
The loader places print.css inside a <style> element. You can also pass a
separate css string to renderPdf. With defaultStyles: false, explicitly
style headings, b, strong, table headings and other elements you use; do not
depend on a browser's default stylesheet. See the
Node API guide for options and
queue guide for bounded background jobs.
Supply fonts and images explicitly¶
The project supplies two unmodified Source Sans 3 font files, with their OFL
license and pinned source manifest. Named font objects use Brand-Regular.ttf
and Brand-Bold.ttf, matching these CSS URLs:
@font-face {
font-family: "Source Sans 3";
src: url("Brand-Regular.ttf");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: "Source Sans 3";
src: url("Brand-Bold.ttf");
font-weight: 700;
font-style: normal;
}
body { font-family: "Source Sans 3"; font-synthesis: none; }
b, strong, th { font-weight: 700; }
Use the font's actual family name consistently in the body and page margin boxes, as this example does. Supply separate faces for the weights and styles your template needs, and review font face selection when changing them.
The HTML's assets/logo.svg resolves to the logo.svg entry in Fullbleed's asset
map. Both font files and the image are passed as bytes. Download remote assets
in your application before rendering; the PDF renderer does not load a website's
assets or use your machine's installed fonts.
Move print settings into CSS¶
| Puppeteer setup | Fullbleed document setup |
|---|---|
A page created with setContent() or goto() |
A completed HTML string passed to renderPdf. |
format, width, height, or page margins |
CSS @page { size: A4; margin: 17mm 18mm 20mm; }. |
| A header/footer template with page-number placeholders | CSS margin boxes with counter(page) and counter(pages). |
| Waiting for browser fonts and image requests | Supply the required font and image bytes in the render call. |
printBackground: true |
Backgrounds defined in the print stylesheet. |
The sample uses a flowing table, a repeated thead, and
break-inside: avoid on rows and the closing total/delivery section. It does not
assign a fixed height to the document or slice it into predetermined pages.
An individual row or closing section taller than a page still needs a layout
decision. Check both your shortest and longest realistic records.
Compare with the optional Puppeteer reference¶
From the project's root:
This separate package installs Puppeteer 25.16.0 and its compatible Chrome.
It prints the same generated HTML, regular/bold font bytes and logo from a
temporary loopback server. Outputs are in ../output/chromium/; its render.json
records the actual browser version and local asset requests. The main Fullbleed
installation does not need this reference package.
The reference enables printBackground and preferCSSPageSize so the shared
stylesheet controls backgrounds and A4 page size. These options default to false
in Puppeteer; see its PDF options and
PDF generation guide. Use Puppeteer's
compatible browser download, or follow the included README to supply a compatible
Chrome already provisioned in CI.
Inspect the retained results¶
Both renderers produced one page for three items and three pages for 24 items in these fixtures. The typography and spacing differ; matching page counts is not a claim of visual parity or support for every browser template.
| Fixture | Fullbleed | Puppeteer / Chrome |
|---|---|---|
| Three items, $85.50 | PDF · preview | PDF · preview |
| 24 items, $896.00 | PDF · page 1 · page 2 · page 3 | PDF · page 1 · page 2 · page 3 |
The verification record retains versions, input/output hashes and checks using two independent PDF readers: every item once and in order, the correct total, embedded regular/bold fonts, visible local logo, repeated table headings, correct page counters and text within the page bounds. The closing total and delivery note remain together on the final page. The download and the JavaScript snippet above are executed in CI.
These are fictional order documents and ordinary PDFs. This example makes no accessibility, archival, print-profile or performance claim. To evaluate your own template, start with CSS coverage, then inspect the actual output with your fonts and representative data.