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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To create a reliable multipage PDF from HTML, define a print stylesheet first, then use a renderer that matches your content. Put page dimensions and margins in @page, hide screen-only controls in @media print, and control section boundaries with break-before, break-after, and break-inside. You can render the result with Puppeteer or Playwright when your page needs browser JavaScript, use WeasyPrint for a Python server workflow, or submit HTML to a hosted converter when you do not want to operate a renderer.

1. Prepare HTML that can paginate

Pagination is easiest when the document has a predictable structure. Use semantic elements such as header, main, section, h1, h2, tables and lists. Keep navigation, cookie notices, live chat, buttons and other screen controls in elements that can be hidden for print output.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Quarterly report</title>
  <link rel="stylesheet" href="print.css">
</head>
<body>
  <nav class="screen-only">Dashboard | Export | Help</nav>
  <main>
    <section class="chapter">
      <h1>Quarterly report</h1>
      <p>Report content…</p>
    </section>
    <section class="chapter">
      <h2>Operations</h2>
      <p>More content…</p>
    </section>
  </main>
</body>
</html>

2. Write a print stylesheet

Print CSS, rather than the screen layout alone, determines how a browser presents a PDF. The @media print block applies only during printing or PDF generation. The @page rule sets the sheet size, orientation and margins.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
/* print.css */
@page {
  size: A4 portrait;
  margin: 18mm 16mm 20mm;
}

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

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

  h1, h2, h3 {
    color: #000;
    break-after: avoid;
    page-break-after: avoid; /* compatibility alias */
  }

  .chapter {
    break-before: page;
    page-break-before: always; /* compatibility alias */
  }

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

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

  img {
    max-width: 100%;
  }

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

break-before controls whether a page, column or region break occurs before an element. Use break-after for the following boundary and break-inside: avoid for compact blocks. The older page-break-* properties are aliases that improve compatibility with older implementations. An avoid value is a preference, not an absolute guarantee: if a block is taller than the remaining page (or taller than a whole page), it must be split.

Choosing paper, margins and orientation

  • Paper: choose a named size such as A4 or Letter, or provide dimensions such as 210mm 297mm.
  • Orientation: use portrait for reports and landscape for wide tables.
  • Margins: leave enough space for headers, footers and printer-safe output. Browser PDF output can use the CSS margins when the renderer is configured to prefer CSS page size.
  • Backgrounds: request background printing explicitly in renderers that expose that option; otherwise colored panels and background images may disappear.

Keeping headings and tables usable

Apply break-after: avoid to headings so a heading does not sit alone at the bottom of a page. Put break-inside: avoid on cards, figures and short tables. For long tables, repeat the header row and allow rows to split when necessary:

thead { display: table-header-group; }
tfoot { display: table-footer-group; }
.long-table tr { break-inside: auto; }

Do not force a page break before every subsection. A forced break is appropriate for a chapter or major part, while normal flow gives better results for ordinary paragraphs.

3. Generate a PDF with Puppeteer

Puppeteer is a browser-automation route for HTML that depends on JavaScript, web fonts, client-side data or browser layout. Its page.pdf() method generates a PDF with the print CSS media type. The method waits for fonts by default.

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.
  1. Install Node.js and create a project: mkdir html-pdf && cd html-pdf && npm init -y.
  2. Install Puppeteer: npm install puppeteer.
  3. Save your HTML as report.html and stylesheet as print.css.
  4. Create make-pdf.js:
const puppeteer = require('puppeteer');
const path = require('node:path');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto(`file://${path.resolve('report.html')}`, {
      waitUntil: 'networkidle0'
    });
    await page.emulateMediaType('print');
    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();
  }
})();

Run node make-pdf.js. For a web page, replace the file:// URL with an HTTPS URL. Use a sufficiently specific waitUntil condition and add an application-level wait for data that appears after the initial network activity. If your CSS contains @page, keep preferCSSPageSize: true so the CSS page size can win over the API default.

4. Generate a PDF with Playwright

Playwright exposes the same basic browser workflow and documents options for paper format, explicit dimensions, margins, page ranges, preferCSSPageSize, printBackground and scale.

  1. Install it with npm init -y && npm install playwright.
  2. Install a browser binary when prompted, or run npx playwright install chromium.
  3. Save this as playwright-pdf.js:
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('http://localhost:3000/report.html', {
      waitUntil: 'networkidle'
    });
    await page.pdf({
      path: 'report.pdf',
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: { top: '18mm', right: '16mm', bottom: '20mm', left: '16mm' },
      scale: 1
    });
  } finally {
    await browser.close();
  }
})();

Playwright’s PDF API uses print media by default. If the page is protected by authentication, establish the session in the browser context before navigation, and do not put credentials in a public URL.

5. Generate a PDF in Python with WeasyPrint

WeasyPrint is a Python-oriented HTML-to-PDF library. Its HTML object can be created from a filename, URL, file object or string, then written with write_pdf(). Its render() method returns a document with individual page objects, which is useful when you need page-level inspection.

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

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

# Or render first when you need page information:
document = HTML(filename='report.html').render()
print(f'pages: {len(document.pages)}')
document.write_pdf('report.pdf')

Install WeasyPrint according to the operating-system instructions for your Python environment, then run the script. A library renderer is convenient for server-side, mostly static HTML, but browser-specific JavaScript behavior is not the same as in Chromium. If the page requires client-side execution, render it in a browser first or choose a browser-based workflow.

6. Use a hosted HTML-to-PDF API

A managed service removes browser installation, patching and process supervision from your application. DocRaptor documents an HTML-to-PDF API powered by Prince and accepts either HTML content or a document URL. Treat this as an operational choice, not a universal performance winner: the available documentation does not establish neutral benchmarks against Puppeteer, Playwright or WeasyPrint.

Before selecting a hosted service, verify how it reaches private pages, handles authentication, stores submitted HTML, reports failures and charges for unsuccessful jobs. Keep sensitive data out of public URLs and use the provider’s documented authentication method.

7. A renderer-selection checklist

Situation Practical first choice Reason
Interactive page, JavaScript data, browser fonts or Chromium-specific layout Puppeteer or Playwright They render the page in a real browser and expose PDF settings.
Python service with controlled HTML and no client-side execution requirement WeasyPrint It provides a direct Python API and page objects after rendering.
No desire to operate a renderer Hosted API such as DocRaptor The conversion process is managed outside your application.

These options expose different feature surfaces; the documentation does not provide a neutral, independently verified ranking of speed or CSS compatibility.

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.

8. Diagnose pagination and output problems

Content is clipped or the page count is surprising

  • Check @page size and margins against the renderer’s format and margin options.
  • Look for fixed-height containers, absolute positioning and transforms that were designed for a screen.
  • Remove an unnecessary forced break and inspect whether a large element is taller than one page.

Background colors or images are missing

Enable the renderer’s background-printing option (printBackground: true in the browser examples). Also confirm that the image URL is reachable from the rendering process and that the asset is not blocked by authentication or a content-security policy.

Fonts are wrong or text reflows

Wait until fonts have loaded before creating the PDF. In a browser workflow, navigate with a suitable wait condition and, when necessary, wait for a known font-loading or application-ready signal. Bundle fonts or make them reachable from the renderer; a local browser and a server may not have the same installed fonts.

A heading is separated from its paragraph

Use break-after: avoid (and its page-break-after alias) on the heading. If the following block cannot fit in the remaining space, the browser may still move or split content.

Dynamic content is absent

Do not generate the PDF immediately after navigation. Wait for the selector that signals completion, an application-defined ready flag or a controlled delay. Network-idle alone may not cover timers, WebSockets or late data requests.

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

Local files work but deployment fails

Check absolute and relative URLs, filesystem permissions, sandbox restrictions, missing browser binaries and outbound network access. Log the final URL, HTTP status, console errors and the renderer’s exception without logging secrets.

Best Value
Python Programming Logo for Programmers T-Shirt
  • Python Programming Language design with distressed logo for Python Software Engineers and Developers.
  • Vintage and Distressed Python Programming Language design.
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

9. Inspect every generated document

Open representative PDFs rather than trusting a successful process exit. Check the first, middle and last pages for clipped edges, blank pages, missing backgrounds, orphaned headings, table overflow, broken links, incorrect orientation and unloaded fonts. Compare output after changing one variable at a time: CSS margins, renderer margins, scale, background printing or a break rule. Browser and library pagination can differ, so validate with the renderer you will run in production.

Or skip the browser setup

ScreenshotNeo can return a PDF from one GET request, with PDF paper size, margins, landscape mode and page ranges available as options. It also supports full-page capture, custom CSS and JavaScript, waiting for a selector, delay or network idle, custom headers and cookies, and signed asynchronous jobs. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

See the parameter details in the ScreenshotNeo documentation. Replace the URL in these examples with your page:

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

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

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 per month with no card. Starter is $5 for 3,000 shots, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000 and Business $249 for 1,000,000; yearly billing gives two months free, and every feature is on every plan. Create a free ScreenshotNeo account.

Frequently Asked Questions

Can CSS guarantee that an element never splits across pages?

No. break-inside: avoid is a preference. An element that cannot fit on one page may still be split.

Should I set margins in CSS, the PDF API, or both?

You can define them in both, but make the relationship intentional. With browser renderers, use preferCSSPageSize when the stylesheet should control page dimensions and avoid accidentally applying two different margin sets.

Why does a PDF contain an extra blank page?

Common causes are a forced break after the final section, content wider than the printable area, or a fixed-height element overflowing. Remove the final break and inspect layout dimensions in the target renderer.

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

Can I use a URL that requires login?

Yes, when the renderer can establish an authenticated session, cookie or authorization header. Configure that access securely and verify that private assets such as fonts and images are reachable.

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.