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.

Fix the problem by first separating two different layouts: a table column header that should repeat on later pages, and a fixed document header that is covering content. For repeating table headings, use a real <thead>, keep the table sections semantic, and add thead { display: table-header-group; } in print CSS. For a separate fixed header, reserve space with a measured top margin or use Puppeteer’s PDF header template. Then test the exact Chromium/Puppeteer version, paper size, margins and markup used in production.

Identify which “header overlap” you have

Look at the PDF symptom before changing CSS. The fix for a table’s column headings is different from the fix for page-level furniture.

Symptom Likely element First check
The column labels appear on page one but disappear when the table continues. The table’s thead is not being repeated. Confirm one semantic table contains a real thead and tbody; inspect print CSS.
A logo, title bar or fixed header covers the first rows on page two or later. An independent fixed-position page header. Measure its rendered height and reserve equivalent space in the PDF layout.
Borders, row backgrounds or text look broken around a page break. Complex table pagination, often rowspans or oversized rows. Reduce the document to a minimal table and inspect rowspans, forced breaks and overflow.

Do not assume that a report describing “the table header is not repeated after a line break” has the same cause as “header section overlaps on each page.” Those are separate layout elements and can occur together.

Make Puppeteer use the intended print layout

page.pdf() uses print CSS by default. Rules inside @media screen will not control the PDF unless you explicitly select screen media. If your design is meant to print, leave the default and inspect every @media print rule. If the PDF should use screen styles, call await page.emulateMediaType('screen') immediately before generating it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless: true});
  const page = await browser.newPage();
  await page.goto('https://example.com/report', {waitUntil: 'networkidle0'});

  // Omit this line for normal print CSS. Use it only when screen CSS is intended.
  // await page.emulateMediaType('screen');

  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    margin: {top: '24mm', right: '12mm', bottom: '20mm', left: '12mm'}
  });
  await browser.close();
})();

Also check whether an @page rule sets a size or margins. Puppeteer’s PDF options include paper dimensions, margins, scale, background printing and CSS page-size precedence. A CSS @page { size: ... } rule can affect the result, so compare it with the options passed to page.pdf().

Fix a table whose headings do not repeat

Use semantic table sections

Put the heading row inside thead and data rows inside tbody. Avoid building a visually table-like layout from unrelated div elements if you need automatic heading repetition.

<table class="invoice-items">
  <thead>
    <tr>
      <th scope="col">Description</th>
      <th scope="col">Quantity</th>
      <th scope="col">Amount</th>
    </tr>
  </thead>
  <tbody>
    <tr><td>Service plan</td><td>1</td><td>$100</td></tr>
    <!-- more rows -->
  </tbody>
</table>

Add the print repetition rule

@media print {
  thead {
    display: table-header-group;
  }

  tr {
    break-inside: avoid;
    page-break-inside: avoid;
  }
}

display: table-header-group is the sensible baseline for repeated headings. It is not a universal guarantee: a Puppeteer issue reported a non-repeating header even with this rule, and that report was marked not reproducible. Treat the result as dependent on your markup, styles and deployed Chromium runtime.

Check rules that change table semantics

Search your stylesheet for declarations that override the browser’s table roles, such as display: block on thead, tbody or tr. Also inspect nested tables, overflow, transforms and positioned ancestors. Create a minimal reproduction with one table, one thead, ordinary tbody rows and enough rows to force a page break. If that works, add your application styles back in small groups.

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

Stop a fixed page header covering later pages

Reserve space for the header

A fixed HTML header is painted independently of normal flow. The body can therefore begin underneath it on every page unless you reserve space. Measure the rendered height in the target viewport and set a top margin large enough to contain it.

const headerHeight = await page.$eval('.page-header', el => el.getBoundingClientRect().height);
const topMargin = `${Math.ceil(headerHeight) + 16}px`;

await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  margin: {top: topMargin, right: '12mm', bottom: '20mm', left: '12mm'},
  printBackground: true
});

Use the same fonts, viewport and content state during measurement and PDF generation. A different font load or responsive breakpoint can change the header height. Confirm the first content line on every page clears the header rather than relying on a guessed margin.

Consider Puppeteer’s PDF header template

For page numbers, titles and other document furniture, compare a fixed HTML header with Puppeteer’s displayHeaderFooter, headerTemplate and footerTemplate options. Templates expose classes for the current page number and total page count. They also work with the PDF margin settings, so reserve enough top or bottom margin for the template’s rendered height.

await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  displayHeaderFooter: true,
  headerTemplate: '<div style="font-size:9px;width:100%;text-align:center">Quarterly report</div>',
  footerTemplate: '<div style="font-size:9px;width:100%;text-align:center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
  margin: {top: '18mm', bottom: '18mm', left: '12mm', right: '12mm'}
});

Do not use both a fixed header and a template for the same visual element unless you deliberately account for both heights.

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

Control page breaks without creating new problems

Apply break avoidance narrowly

break-inside: avoid (and its older alias page-break-inside: avoid) expresses a preference, not an absolute command. CSS paged-media rules allow the user agent to relax avoidance when there is no workable break point. A row taller than the available page area cannot remain intact, and forced breaks can take precedence.

Apply avoidance to ordinary rows or small cards that can realistically fit on one page. Do not put it on an entire long table: that can produce large blank areas or unexpected pagination.

Inspect forced breaks and oversized content

  • Search for break-before, break-after, page-break-before and page-break-after.
  • Check images, code blocks and nested components whose height exceeds the printable area.
  • Remove accidental min-height values and excessive padding while debugging.
  • Verify that a parent’s overflow: hidden or positioned layout is not clipping content at a break.

Handle rowspans cautiously

Tables containing rowspan can show uneven borders or row styling at page boundaries. A reported Puppeteer case described such artifacts despite multiple CSS workarounds. If the minimal non-rowspan table works, test a flattened data model, split the logical group into separate tables, or accept that the complex structure needs a document-specific workaround.

A repeatable debugging procedure

  1. Lock the environment. Record Puppeteer and Chromium versions, viewport, URL, fonts, paper size, margins, scale and print-background setting.
  2. Confirm media. Inspect computed styles under print media and check whether your code calls emulateMediaType('screen').
  3. Inspect the DOM. Verify one table with a genuine thead and tbody; confirm no print rule changes their display roles.
  4. Separate the layouts. Temporarily hide the fixed page header. If the table now paginates correctly, solve the reserved-space problem independently.
  5. Render a minimal reproduction. Use a plain table with enough rows for two pages, then add rowspans, nested blocks and application CSS one at a time.
  6. Check pagination constraints. Remove forced breaks, test oversized rows and apply break-inside: avoid only to elements that can fit.
  7. Inspect the actual PDF. Test at the production paper size and margins, not only in a browser preview. Repeat after deployment in the exact runtime that generates customer PDFs.

Common failures and targeted fixes

Failure Cause to investigate Fix
thead still appears once Non-semantic markup, overridden display, or runtime-specific behavior. Use real table sections, restore table display roles, and test a minimal reproduction in the deployed runtime.
Rows are hidden beneath a logo on page two Fixed header has no reserved flow space. Measure its height and increase the PDF top margin, or move it to a PDF header template.
A row splits despite avoid The row is too tall, a forced break wins, or the user agent relaxes the preference. Shorten the row, remove forced breaks, or split the content into smaller units.
Borders jump around a break Rowspan or complex border painting. Flatten or split the table for print and compare against a minimal case.
Screen preview differs from PDF Print media is active by default. Fix @media print, or explicitly call emulateMediaType('screen') when that is the intended design.

Performance, reliability and cost considerations

Pagination is sensitive to every input that affects layout: font availability, image dimensions, viewport, scale, CSS page size, margins and Chromium version. Wait for the resources your document needs before calling page.pdf(); otherwise late font or image changes can move a heading onto a different page. Keep a PDF fixture in automated tests that includes a multi-page table, a fixed header, an oversized row and (if applicable) a rowspan.

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

When a layout changes, compare rendered PDFs rather than only checking that a file was produced. A successful HTTP response or nonempty PDF does not prove that headings repeated or that content was not covered.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean screenshot or PDF rather than maintaining Chromium pagination code, ScreenshotNeo provides a website screenshot API and MCP server. It accepts a URL with one GET request and can return PNG, JPEG, WebP or PDF. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers.

The API includes full-page capture with lazy images loaded, element selection by CSS selector, dark mode, device presets and custom viewports, retina scale, PDF paper size/margins/orientation/page ranges, custom CSS and JavaScript, clicks, waits, request and resource blocking, headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs.

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}`);

See the ScreenshotNeo documentation for request options. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is included on every plan. An MCP server supplies take_screenshot, get_page_info and capture_pdf tools to Claude, Cursor and other MCP clients.

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

Create a free ScreenshotNeo account to use the 1,000 monthly shots without a card.

Best Value
The SQL Programming Language: .
  • Used Book in Good Condition

FAQ

Should I force a page break before every table header?

No. Let the semantic table header repeat naturally. Forced breaks are useful for deliberate document sections, but they can override break-avoidance rules and create blank space.

Can CSS guarantee that a very tall row stays together?

No. If the row cannot fit in the available printable area, the paged layout engine may split it despite break-inside: avoid.

Why does the same HTML behave differently after deployment?

PDF layout depends on the exact Puppeteer/Chromium build, fonts, viewport, options and loaded resources. Reproduce with the deployed runtime and inputs rather than relying on a local browser preview.

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

Frequently Asked Questions

Should I force a page break before every table header?

No. Let a semantic table header repeat naturally; forced breaks are for deliberate sections and can create blank space.

Can CSS guarantee that a very tall row stays together?

No. Paged layout may split a row that cannot fit, even with break avoidance.

Why does identical HTML paginate differently in production?

The exact Puppeteer/Chromium build, fonts, viewport, options and loaded resources all affect PDF layout.

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.

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