Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use print CSS to control the document, then render that HTML with a browser engine or an HTML-to-PDF library. Define paper size and margins with @page, hide screen-only elements in @media print, and use break-before, break-after, and break-inside to manage pagination. For JavaScript-heavy pages, Puppeteer or Playwright provide browser-quality output; for a Python server workflow, WeasyPrint can convert HTML directly.

1. Build HTML that can paginate

Start with semantic sections rather than a single oversized container. Headings, paragraphs, lists, tables, and figures give the renderer meaningful boxes at which it can place page breaks. Keep content inside the document’s normal flow; absolute positioning is useful for a cover or watermark, but it can overlap or disappear when content grows.

A minimal multipage document

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Quarterly report</title>
  <link rel="stylesheet" href="print.css">
</head>
<body>
  <header class="screen-only">Dashboard navigation</header>
  <main>
    <section class="chapter">
      <h1>Quarterly report</h1>
      <p>Summary content…</p>
    </section>
    <section class="chapter">
      <h2>Results</h2>
      <p>Detailed results…</p>
      <table>…</table>
    </section>
  </main>
</body>
</html>

2. Add print CSS for paper, margins, and breaks

Print rules participate in normal CSS specificity and precedence. If an existing screen rule wins, increase the selector’s specificity or place the print stylesheet later in the cascade. The @page rule sets the printable page box; @media print changes the document’s appearance when it is printed or converted to PDF.

@page {
  size: A4 portrait;
  margin: 18mm 16mm 20mm;
}

@media print {
  .screen-only,
  nav,
  .cookie-banner,
  .chat-widget,
  .print-button {
    display: none !important;
  }

  body {
    margin: 0;
    color: #111;
    background: #fff;
    font: 10.5pt/1.45 Arial, sans-serif;
  }

  h1, h2, h3 {
    break-after: avoid;
  }

  .chapter {
    break-before: page;
  }

  .chapter:first-child {
    break-before: auto;
  }

  figure, table, pre, blockquote {
    break-inside: avoid;
  }

  a {
    color: inherit;
    text-decoration: none;
  }
}

/* Legacy name remains useful for older engines. */
.chapter {
  page-break-before: auto;
}
.chapter + .chapter {
  page-break-before: always;
}

break-before: page forces a new page before a box. break-inside: avoid is a preference, not an absolute guarantee: if a table or paragraph is taller than the available page, the renderer must split it. The older page-break-before property is aliased for compatibility.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Page size and orientation

Use named sizes such as A4 or Letter, optionally followed by portrait or landscape. You can also specify dimensions, for example size: 210mm 297mm. Leave enough margin for the target printer or binding. CSS page margins and renderer options can both exist; decide which is authoritative to avoid surprises.

Backgrounds, images, and fonts

Background colors and images are commonly disabled by print defaults. Enable background printing in the renderer when the design depends on them. Use absolute or data URLs for assets when a renderer cannot resolve relative paths, and wait for web fonts before saving. A missing font can change line wrapping and therefore every later page break.

3. Render with Puppeteer (Node.js)

Puppeteer’s page.pdf() generates a PDF using the print CSS media type. The following script loads a local file, waits for fonts, and writes a multipage PDF.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({headless: 'new'});
try {
  const page = await browser.newPage();
  await page.goto('file:///absolute/path/report.html', {
    waitUntil: 'networkidle0'
  });
  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'}
  });
} finally {
  await browser.close();
}

Install Puppeteer with npm install puppeteer. For a web URL, replace the file:// address and wait for the page’s data to finish loading. If the page needs authentication, set cookies or headers before navigation. The PDF method waits for fonts by default, but explicitly awaiting document.fonts.ready makes the dependency visible in your code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When to choose Puppeteer

  • The document uses client-side JavaScript, complex browser layout, or components already tested in Chromium.
  • You need browser APIs such as cookies, custom headers, or interaction before capture.
  • You can run a browser process in your server, container, or build job.

4. Render with Playwright (Node.js)

Playwright exposes the same fundamental PDF workflow and documents options for paper format, dimensions, margins, page ranges, scale, background printing, and whether CSS page size should take precedence.

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage({viewport: {width: 1280, height: 900}});
  await page.goto('https://example.com/report', {waitUntil: 'networkidle'});
  await page.evaluate(() => document.fonts.ready);
  await page.pdf({
    path: 'report.pdf',
    format: 'Letter',
    landscape: false,
    printBackground: true,
    preferCSSPageSize: true,
    margin: {top: '0.7in', right: '0.65in', bottom: '0.8in', left: '0.65in'},
    scale: 1
  });
} finally {
  await browser.close();
}

Use page.pdf({pageRanges: '1-3'}) when you need only selected pages. If the PDF ignores your @page dimensions, check preferCSSPageSize; when true, CSS wins over a format or width/height supplied to the API.

5. Convert HTML with WeasyPrint (Python)

WeasyPrint provides a server-side library path. Its HTML object accepts a filename, URL, file object, or string, and write_pdf() writes the result. Its render() method returns a document whose page objects can be inspected.

from weasyprint import HTML

HTML(filename='report.html', base_url='.').write_pdf('report.pdf')

# From an HTML string, keep a base URL so relative assets resolve.
html = HTML(string='<h1>Report</h1><p>Body</p>', base_url='.')
document = html.render()
print(f'pages: {len(document.pages)}')
document.write_pdf('string-report.pdf')

Use this route when your application is Python-based and the content does not require a full browser’s JavaScript execution. Test the CSS your document relies on; browser and library pagination are not identical, especially for advanced layout, scripts, and interactive components.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

6. Choose a rendering path

Path Best fit Important controls Operational trade-off
Puppeteer Chromium-rendered pages and JavaScript applications page.pdf(), print CSS, backgrounds, margins, CSS page size Operate a browser process
Playwright Browser automation with explicit PDF and page-range options Format or dimensions, margins, scale, preferCSSPageSize, printBackground Operate browser binaries and manage jobs
WeasyPrint Python services with mostly server-rendered HTML/CSS HTML(...).write_pdf(), render() and page objects Validate CSS and asset support for your design
Hosted API Teams that do not want to run a renderer Submit HTML or a document URL; provider-specific settings External service, network and data-handling considerations

There is no neutral, universal performance winner established by the documentation. Compare JavaScript requirements, language integration, page and break controls, background and font handling, and whether your team wants to patch and scale a renderer.

7. A repeatable production workflow

  1. Prepare the source. Make URLs, fonts, images, and data available to the rendering environment. Add a base URL for relative assets.
  2. Write print rules. Hide navigation and controls, set @page, and add intentional chapter breaks.
  3. Configure the renderer. Choose paper format, margins, background printing, scale, and CSS-page-size precedence.
  4. Wait for readiness. Wait for network activity, application-specific selectors, images, and document.fonts.ready.
  5. Inspect every page. Look for clipped edges, overflow, blank pages, broken tables, missing backgrounds, and headings stranded at the bottom.
  6. Regress representative documents. Keep a short, long, image-heavy, table-heavy, and multilingual fixture. Pagination can change when a font, browser, or CSS rule changes.

8. Troubleshooting common failures

Content is cut off or extends beyond the page

Check for fixed heights, absolute positioning, large unbreakable elements, and a renderer margin that conflicts with @page. Remove hard-coded screen heights and allow long words or code blocks to wrap. A block taller than one page cannot honor break-inside: avoid.

Every section starts on a new page, including the cover

A broad break-before: page selector is probably matching the first section. Override the first item with break-before: auto, as in the example, and inspect legacy page-break-before rules.

Background colors or images are missing

Enable printBackground in Puppeteer or Playwright. Also confirm the asset URL is reachable from the renderer and that the CSS rule is inside (or applies to) print media.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fonts are wrong and pagination shifts

Make the font files accessible, wait for document.fonts.ready, and avoid saving immediately after navigation. If a remote font is unreliable, package it with the job or use a dependable fallback and test the resulting line lengths.

JavaScript content is blank

Do not save immediately after the initial response. Wait for a meaningful selector, application-ready signal, or network idle. If the page requires a click, perform it before calling the PDF method.

Local images or styles do not load in Python

Pass a correct base_url to WeasyPrint, use valid file paths, and check permissions. A document created from an HTML string has no useful relative URL base unless you provide one.

Tables split in unusable places

Apply break-inside: avoid to small tables or row groups where supported, but do not apply it to a table that can exceed a page. For very long tables, repeat headers with table-specific print CSS and accept row-level splits where necessary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo can capture a page or create a PDF through one request. Its PDF options include paper size, margins, landscape mode, and page ranges; it also supports custom CSS, JavaScript, waiting for a selector or network idle, cookies, headers, user agents, and authentication.

cURL:

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, add the service’s PDF parameters described in the ScreenshotNeo documentation and choose a PDF response format.

Python:

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)

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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = Buffer.from(await res.arrayBuffer());

Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; 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 screenshots a month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

9. Reliability, performance, and cost considerations

  • Browser startup: Reuse a browser process for batches instead of launching one per document, while isolating pages and closing them after each job.
  • Network determinism: Remote ads, trackers, fonts, and third-party widgets can delay or alter pagination. Block unnecessary requests or self-host critical assets.
  • Memory: Large images and very long pages increase browser memory. Resize source images and process large batches with bounded concurrency.
  • Caching: Cache immutable HTML and assets, but invalidate when data, CSS, fonts, or browser versions change.
  • Security: Treat user-supplied HTML and URLs as untrusted. Sandbox browser jobs, restrict network access where possible, and prevent access to internal metadata endpoints.
  • Validation: Record renderer version, CSS revision, input URL or document hash, and output page count so a changed PDF can be diagnosed.

Frequently Asked Questions

Can CSS guarantee that a heading and its paragraph stay together?

No. break-after: avoid and break-inside: avoid are pagination preferences. The renderer may still split content when it cannot fit the requested block on a page.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use a browser or WeasyPrint for JavaScript-rendered HTML?

Use a browser renderer when the final content depends on client-side JavaScript or browser APIs. WeasyPrint is a better fit for server-rendered HTML when you do not need JavaScript execution.

How can I generate only selected PDF pages?

Use a renderer option that supports page ranges, such as Playwright’s pageRanges. The exact syntax and availability depend on the renderer you choose.

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.