How to Optimize HTML for PDF Printing
Build print CSS that fits the page, controls page breaks, and produces predictable PDFs in a browser or automated workflow.
To optimize HTML for PDF printing, create a print-specific layout, set page size and margins deliberately, control page breaks selectively, and inspect the generated PDF in the same browser engine and settings used in production. Screen layouts have different constraints from paper. A dependable PDF comes from preparing the content for page-sized areas, then checking the rendered artifact for clipping, awkward splits, missing assets, and unexpected browser defaults.
This guide covers browser print dialogs and automated Chromium PDF generation with Puppeteer and Playwright. The examples use CSS for layout and page geometry, then show how to save PDFs with each Node.js library.
1. Create a print stylesheet
Put print overrides in a dedicated stylesheet or an @media print block near the end of your main stylesheet. A linked stylesheet can be restricted to printing with media="print"; print media rules also apply when a browser generates a PDF. Keep the specificity of existing application styles in mind: a later rule does not always win if an earlier selector is more specific. MDN’s printing guide documents print stylesheets, media queries, and page rules.
<link rel="stylesheet" href="/css/app.css">
<link rel="stylesheet" href="/css/print.css" media="print">
Start by removing controls and chrome that have no meaning on paper. Keep document content, contact details, references, and other information the reader needs after printing. Avoid hiding broad containers if they also contain the primary content.
@media print {
/* Hide interactive or repeated screen-only interface. */
.site-header,
.site-nav,
.sidebar,
.cookie-controls,
.toolbar,
.no-print {
display: none !important;
}
html,
body {
background: #fff !important;
color: #111 !important;
font: 10.5pt/1.45 Georgia, "Times New Roman", serif;
}
main,
article,
.content {
width: auto !important;
max-width: none !important;
margin: 0 !important;
padding: 0 !important;
overflow: visible !important;
}
a {
color: inherit;
text-decoration: underline;
}
/* Show destinations when a printed link's URL is useful. */
a[href^="http"]::after {
content: " (" attr(href) ")";
font-size: 0.85em;
overflow-wrap: anywhere;
}
/* Do not append a URL to an image-only link or very long tracking URL. */
a[href^="http"].no-print-url::after {
content: none;
}
}
Only expand link destinations where they help the printed reader. Appending long URLs to every link can make a document difficult to scan. Use semantic classes to suppress URL annotations selectively, or omit this rule entirely.
Make screen layouts flow on paper
Fixed widths, viewport-height panels, sticky elements, scroll containers, and multi-column application layouts often cause clipping or excessive whitespace in a PDF. In print, let the main content use the available page width, remove fixed heights, and make scrollable regions visible. For dense dashboards, consider a dedicated print view that turns cards into a linear sequence and presents important values as labeled text or tables.
@media print {
.app-shell {
display: block !important;
height: auto !important;
min-height: 0 !important;
}
.scroll-panel,
.table-wrapper {
height: auto !important;
max-height: none !important;
overflow: visible !important;
}
.dashboard-grid {
display: block !important;
}
.dashboard-card {
break-inside: avoid;
margin-block: 0 1rem;
}
}
A stylesheet alone cannot make every interactive interface meaningful on paper. For content that depends on expanded tabs, menus, or virtualized rows, prepare the desired content before printing or create a purpose-built print route.
2. Choose paper size, orientation, and margins
Use @page when the document stylesheet should define page geometry. Common choices include A4 or US Letter; use landscape for wide tables or diagrams only when that improves readability. Margins reserve space for the document and any headers or footers. Units such as millimeters, inches, and points are appropriate for physical page geometry.
@page {
size: A4 portrait;
margin: 18mm 16mm 20mm;
}
@media print {
.wide-table-page {
page: wide;
}
}
@page wide {
size: A4 landscape;
margin: 14mm;
}
The named page example only helps if the relevant element is assigned to that page and the target renderer supports the feature as expected. For the simplest cross-workflow setup, use one page size and orientation throughout. Browser print dialogs and PDF APIs can also set paper size and margins. Avoid giving CSS and the API conflicting values unless you have deliberately chosen which source takes precedence.
In Playwright, preferCSSPageSize: true lets CSS @page size take priority over the API’s format, width, or height. Otherwise the API can scale content to fit its configured paper size. Browser dialog behavior and automation API defaults are separate concerns, so validate the exact path used by your application. Playwright’s PDF API reference lists its paper and margin options.
Headers and footers need space
Browser-generated headers and footers may occupy the page margins. If you use them, leave enough margin and inspect the first page as well as later pages. Chrome’s guidance covers page margin boxes and browser printing behavior; margin-box support described there is Chrome-specific and should not be assumed across all browsers. Chrome for Developers: print margins.
For automated output, custom header and footer templates may be available in the PDF library. Check the installed version’s API, reserve enough top and bottom space, and verify that page numbers and titles do not overlap document content.
3. Add page breaks without losing content
Use modern fragmentation properties to request where content should begin or end, and to discourage breaks inside compact units such as a short card, figure, or signature block.
@media print {
.chapter {
break-before: page;
}
.chapter:first-child {
break-before: auto;
}
h1,
h2,
h3 {
break-after: avoid-page;
}
figure,
.summary-card,
.signature-block {
break-inside: avoid-page;
}
table {
width: 100%;
border-collapse: collapse;
}
thead {
display: table-header-group;
}
tr {
break-inside: avoid-page;
}
.new-page {
break-before: page;
}
}
The legacy declarations page-break-before, page-break-after, and page-break-inside may still appear in existing stylesheets. Prefer break-before, break-after, and break-inside in new code; add legacy aliases only when your supported renderer requires them.
break-inside: avoid-page is a preference, not a way to fit arbitrarily tall content on one page. A long section, table, or image must split across pages or overflow if it cannot fit in the available space. Apply avoidance to small blocks; avoid placing it on entire chapters or long tables, which can create large blank gaps or still fragment in ways you did not expect. The older W3C CSS Print Profile, marked obsolete, explains this preservation caveat for content taller than a page: W3C CSS Print Profile.
Prevent orphaned headings and table damage
Keep headings with the next block where possible, repeat table headers, and avoid splitting a short row. Browsers do not guarantee identical fragmentation across engines, especially for complex tables, flexbox, grid, and nested overflow. If a table row is taller than a page, it cannot remain intact; shorten or redesign the row rather than expecting a break rule to make it fit.
4. Tune typography, colors, and images
Choose a type size that remains readable at the intended paper size. Remove low-contrast screen colors, large decorative shadows, and backgrounds that consume ink without adding information. Do not use color alone to distinguish status or categories; pair it with labels, shapes, or patterns. If readers may print in grayscale, inspect a grayscale copy.
Browsers can adjust colors for printing. Chromium-based PDF APIs use print media by default and may modify colors; -webkit-print-color-adjust: exact can request stronger color fidelity when backgrounds or brand colors matter. It is a request, and print dialog settings can still affect the result. In Playwright, background graphics are off by default; enable printBackground when the design depends on CSS backgrounds. Puppeteer’s PDF documentation and the Playwright API describe these renderer behaviors and options.
@media print {
.brand-panel,
.chart-legend {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
img,
svg,
canvas {
max-width: 100% !important;
}
img {
height: auto;
}
pre,
code {
overflow-wrap: anywhere;
white-space: pre-wrap;
}
}
Use image sources with sufficient resolution for their printed size. Ensure images and web fonts have loaded before calling the PDF API; a browser can otherwise capture fallback fonts or empty image boxes. For a chart rendered to canvas, wait for the application’s rendering to finish, and include an accessible text or tabular equivalent when the data needs to remain usable beyond the image.
5. Generate a PDF with Puppeteer
Puppeteer’s page.pdf() renders using print CSS. If you specifically need screen media, call page.emulateMediaType('screen') first; for a print-optimized document, leave print media active. Install Puppeteer in a Node.js project, save the following as pdf.mjs, and run node pdf.mjs. The URL must be reachable by the machine running the script.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/report', {
waitUntil: 'networkidle0',
timeout: 60_000,
});
// Wait for web fonts before layout is captured.
await page.evaluate(() => document.fonts.ready);
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
margin: {
top: '18mm',
right: '16mm',
bottom: '20mm',
left: '16mm',
},
displayHeaderFooter: false,
});
} finally {
await browser.close();
}
If CSS owns the geometry, keep preferCSSPageSize enabled and do not set conflicting paper dimensions in code. If the job should use an API-defined format, configure the API and remove or align the CSS @page size. Puppeteer’s PDF generation documentation describes print media and color behavior: Puppeteer Page.pdf().
6. Generate a PDF with Playwright
Playwright’s page.pdf() also uses print CSS by default. Save this as pdf.mjs after installing the Playwright package and its browser, then run node pdf.mjs. The exact install and browser setup commands depend on the project’s chosen Playwright version; follow its current installation instructions.
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto('https://example.com/report', {
waitUntil: 'networkidle',
timeout: 60_000,
});
await page.evaluate(() => document.fonts.ready);
await page.pdf({
path: 'report.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
margin: {
top: '18mm',
right: '16mm',
bottom: '20mm',
left: '16mm',
},
displayHeaderFooter: false,
tagged: true,
});
} finally {
await browser.close();
}
Playwright’s documented options include paper formats such as Letter and A4, margins, background printing, CSS page-size preference, headers and footers, and tagged PDF output. Availability and defaults can change with the installed version, so check the API reference for the version pinned in your project. Tagged output is not a substitute for checking document structure and accessibility. Playwright Page PDF API.
7. Print from the browser
For a user-triggered print, keep the browser’s print dialog in the workflow so the reader can choose their destination and printer settings. A minimal button can call window.print(); the print stylesheet is activated by the browser.
<button type="button" class="no-print" onclick="window.print()">
Print or save as PDF
</button>
In the print dialog, select the intended paper size and orientation, check scaling, inspect margins and browser headers/footers, and enable background graphics only when they carry necessary meaning. Dialog labels and defaults vary by browser and platform. If you need to prepare the DOM just before printing, the browser exposes beforeprint and afterprint events, but prefer CSS for presentation changes that do not need JavaScript.
8. Validate the rendered PDF
Do not judge print quality from the source HTML alone. Render the document with the target browser and settings, then inspect the PDF at its intended page size. Check representative pages, including the first and later pages, longest sections, wide tables, large images, and pages with headers or footers.
- Confirm the page geometry: paper size, orientation, margins, and scaling match the intended output.
- Check clipping and overflow: look for content cut at the right edge, hidden scroll regions, fixed-height panels, and long unbroken strings.
- Review fragmentation: check headings stranded at page bottoms, split rows, blank pages, oversized blocks, and repeated table headers.
- Check assets: confirm web fonts, images, SVGs, charts, and background graphics appear at usable resolution.
- Review navigation and meaning: links should remain understandable; color should not be the only signal; verify page order and any generated headers or footers.
- Repeat in the production engine: use the same browser version, options, paper size, and media mode as the deployed job.
Browser engines do not implement every paged-media feature identically. Current Chrome guidance notes that support has not been uniform across web browsers. Treat output as renderer-specific and keep a representative PDF as a visual regression artifact when print output is part of a release-critical workflow.
9. Troubleshooting common PDF printing problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Content is cut off at the right edge | Fixed-width content, a wide table, or overflow clipping exceeds the printable area. | Remove fixed widths in print CSS, set wide content to a deliberate landscape page if supported, or restructure the table. Inspect at the target paper size. |
| Navigation or controls still appear | The print rule does not match the element, a more specific rule wins, or the stylesheet is not loaded. | Inspect computed print styles in the target browser; move overrides later, increase selector specificity where needed, or verify the print stylesheet URL. |
| Background colors are missing | Background printing is disabled in the print dialog or API. | Enable background graphics in the dialog or set printBackground: true in the PDF API. Use print color adjustment for elements whose colors carry meaning. |
| A section starts on a new page unexpectedly | A forced break, broad break-inside: avoid, or an oversized preceding block caused fragmentation. |
Search print rules for break declarations. Remove forced breaks or apply avoidance to smaller components. |
A long block still splits despite break-inside: avoid |
The block is taller than the printable page area; avoid rules cannot fit it intact. | Let it split, shorten it, or divide it into meaningful smaller sections. |
| Fonts look different or text shifts | The web font failed to load or PDF generation began before font loading completed. | Check font requests and licensing/deployment paths; await document.fonts.ready before generating. |
| Images or charts are blank | Assets were still loading, lazy content was not activated, or the chart had not rendered. | Wait for application readiness and assets explicitly. Trigger required lazy content before PDF generation and confirm the page has no failed requests. |
| Extra blank page appears | Content slightly exceeds the printable height, forced page breaks accumulate, or fixed dimensions create overflow. | Inspect the final elements and break rules; reduce excess margins or spacing and remove viewport-based fixed heights. |
| Header/footer overlaps content | Insufficient margin was reserved for generated or custom header/footer content. | Increase top or bottom margin, shorten the template, and inspect the first page separately from later pages. |
| Puppeteer or Playwright times out on navigation | The page never reaches the selected lifecycle state because of long-lived connections or slow requests. | Use a less strict lifecycle event when appropriate, then wait for a specific application-ready selector or condition. Keep a bounded timeout and handle failure explicitly. |
10. Performance, reliability, and cost
PDF generation cost is mostly operational: browser startup, page load, JavaScript execution, font and image downloads, rendering, and writing the file. Reuse a browser process for batches where your architecture permits, while isolating pages and closing them reliably. Set timeouts for navigation and application readiness, and close browser resources in a finally block. Avoid waiting indefinitely for network idle on pages with analytics, polling, or persistent connections; wait for the content your document actually requires.
For repeatable output, pin the browser and library versions in the deployment environment, use stable fonts and assets, set paper geometry explicitly, and keep a small set of representative documents for review. Different browsers or operating systems can render font metrics and pagination differently. A successful API call only confirms that a PDF was produced; it does not prove the document is complete or legible.
Reduce avoidable work by removing unnecessary third-party scripts from a print route, limiting oversized images, and generating only after the required content is ready. If you generate many PDFs, measure your own workload before choosing concurrency: more parallel browser pages can increase memory and CPU pressure and may make rendering less reliable. No universal throughput figure applies across document sizes and deployment environments.
Or skip the browser setup
If you need a quick visual capture of a page while tuning its print presentation, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. For PDF generation, use its PDF output options; for an image capture, the one-call request below returns a screenshot. The [ScreenshotNeo documentation](https://screenshotneo.com/docs/) describes the API and its parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/report -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/report"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/report'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);
- Cookie banners, newsletter popups, and chat widgets are removed before the shot.
- Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use screenshot, page information, and PDF capture tools.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
FAQ
Does print CSS also apply when saving a page as PDF?
Yes. Browser PDF generation through Puppeteer and Playwright uses print media by default. A browser’s own print dialog also applies print styles.
Should I use CSS or PDF API options for page size?
Choose one source of truth or set both to the same geometry. If CSS should control size in Playwright, use preferCSSPageSize; check the corresponding setting in your chosen renderer.
Can I force every component to stay on one page?
No. Break avoidance is advisory, and content taller than the available page area must continue or overflow. Use it on compact elements only.
Will the same HTML produce identical PDFs in every browser?
No. Page fragmentation, margins, fonts, color handling, and paged-media support can differ. Validate output in the target engine and environment.
How do I add a page break before a section?
Give the section a class and set break-before: page in print CSS, then inspect the generated pages for unwanted blank space or cascading breaks.


