Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk9 min

How to Fix Puppeteer PDF Page Break Differences on Heroku

Puppeteer PDFs paginate differently on Heroku when print media, fonts, browser versions, dependencies, or page geometry differ. Use this reproducible workflow to find and fix the first divergence.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If a Puppeteer PDF breaks at different points on Heroku than on your computer, do not begin by adding arbitrary break-before rules. Make the rendering inputs identical first: print media, fonts, browser and Puppeteer versions, paper geometry, margins, scale, HTML data, and Linux dependencies. Then wait for the intended fonts, generate both PDFs from the same fixture, and locate the first page where the layouts diverge.

Why Heroku can paginate the same page differently

A PDF is produced by a browser layout engine, not by copying the screen. Small changes in available width or height can move a line, which changes every block below it and eventually shifts page breaks.

Puppeteer uses print CSS by default

page.pdf() generates the document with the print CSS media type. Rules inside @media print can change display, dimensions, colors, overflow, and visibility compared with the screen view. Heroku may therefore appear to “break” a page differently even when the HTML is identical.

If the PDF is intentionally supposed to match screen styling, select it explicitly before printing:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.emulateMediaType('screen');
const pdf = await page.pdf(pdfOptions);

Do not use screen media as a workaround for an accidental print stylesheet. Inspect and correct the print rules instead.

Fonts change line wrapping

A missing font is silently replaced by an available fallback. Different glyph widths alter line wrapping, element heights, and therefore pagination. This is especially visible with CJK and other non-Latin scripts. Puppeteer waits for document fonts by default, but waiting cannot install a font that is absent from the Heroku slug.

Browser and Linux environments are not identical

Different Chromium builds, Puppeteer versions, font libraries, or shared Linux libraries can produce different layout results. Heroku’s Puppeteer guidance notes that extra dependencies may be required, that requirements vary with the browser package, and that launching may require the --no-sandbox argument in that environment.

Paper geometry leaves less or more room

Puppeteer’s PDF defaults matter. format defaults to Letter, scale to 1, preferCSSPageSize to false, and margins are undefined (no margins set). A different paper size, margin, or scale changes the usable page area. A CSS @page size can also compete with API options.

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

First, create a reproducible comparison

  1. Save one representative HTML/data fixture, including the exact records, locale, timezone, and images used by the failing document.
  2. Generate a PDF locally and on Heroku with the same URL, input data, wait conditions, and PDF options. Download both files without post-processing.
  3. Record the package-lock entry, puppeteer.version() result, actual Chromium/Chrome version, Heroku stack, buildpacks, environment variables, loaded fonts, print CSS, and complete page.pdf() options.
  4. Compare the first page where content differs. The first divergence is more useful than counting the final number of pages: inspect the preceding element’s computed size, font, margin, and page-break properties.
  5. Reduce the fixture to the smallest HTML that still reproduces that first divergence. Change one variable at a time—fonts, geometry, print CSS, then browser stack.

This process distinguishes a rendering-input problem from a genuinely incorrect manual break rule.

Make PDF media and CSS page rules deliberate

Audit print-only rules

Search every stylesheet for @media print, @page, display:none, width declarations, overflow, and page-break properties. Check whether a production-only stylesheet, asset URL, or feature flag is changing the print tree.

@page {
  size: A4;
  margin: 14mm 12mm;
}

@media print {
  .screen-only { display: none !important; }
  .invoice { break-inside: avoid; }
}

Choose one source of page size

Use API geometry when the application owns the PDF contract:

const pdfOptions = {
  format: 'A4',
  margin: {
    top: '14mm',
    right: '12mm',
    bottom: '14mm',
    left: '12mm'
  },
  preferCSSPageSize: false,
  scale: 1,
  printBackground: true,
  waitForFonts: true
};

Use CSS geometry when designers own it, and allow it to win:

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.
const pdfOptions = {
  preferCSSPageSize: true,
  printBackground: true,
  scale: 1,
  waitForFonts: true
};

When format is supplied, it takes priority over width and height. With preferCSSPageSize: true, CSS @page size takes priority instead. Do not mix competing definitions casually. Keep scale identical in both environments; its documented range is 0.1–2, with 1 as the default.

Use a deterministic Puppeteer PDF routine

The following example makes the important choices explicit. Adapt the URL, selector, and PDF options to your application.

const puppeteer = require('puppeteer');

async function createPdf(url) {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });

  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1280, height: 900, deviceScaleFactor: 1 });
    await page.goto(url, { waitUntil: 'networkidle0', timeout: 90000 });

    // Keep this enabled unless you have a documented reason not to.
    await page.evaluate(async () => {
      await document.fonts.ready;
    });
    await page.waitForSelector('[data-pdf-ready]', { timeout: 30000 });

    // page.pdf() uses print media by default. Select screen only deliberately.
    // await page.emulateMediaType('screen');

    const pdf = await page.pdf({
      format: 'A4',
      margin: { top: '14mm', right: '12mm', bottom: '14mm', left: '12mm' },
      preferCSSPageSize: false,
      scale: 1,
      printBackground: true,
      waitForFonts: true,
      path: 'output.pdf'
    });
    return pdf;
  } finally {
    await browser.close();
  }
}

createPdf(process.argv[2]).catch(error => {
  console.error(error);
  process.exitCode = 1;
});

waitForFonts is true by default in Puppeteer’s PDF API. Keeping it explicit makes reviews easier, but it does not solve missing font files or a failed font request. The readiness marker in this example should be set by your application only after data and critical assets are present.

Make fonts identical on Heroku

  1. List the font families and exact files used by the document, including weight and style variants. A regular face substituted for a semibold face can change wrapping.
  2. Ship the required font files with the application or install them through the deployment method supported by your current Heroku stack and browser package.
  3. Use stable, local @font-face URLs and verify that requests return successfully in the deployed app.
  4. Wait for document.fonts.ready (and keep Puppeteer’s waitForFonts enabled) before calling page.pdf().
  5. For CJK documents, check every required character range and fallback family; a partially available family can create mixed metrics.

To diagnose rather than guess, log loaded faces in the page and compare computed styles for a representative text node:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const fontReport = await page.evaluate(() => ({
  ready: document.fonts.status,
  faces: [...document.fonts].map(face => ({
    family: face.family,
    weight: face.weight,
    style: face.style,
    status: face.status
  })),
  sample: getComputedStyle(document.querySelector('[data-font-sample]'))?.font
}));
console.log(fontReport);

Align Heroku’s browser runtime

Pin what you can compare

Keep the Puppeteer dependency and lockfile stable while diagnosing. Capture the actual browser version from each environment rather than assuming that a package version implies a particular Chromium build. Also record the Heroku stack and every buildpack involved.

Install compatible libraries and buildpack support

The current Puppeteer Heroku troubleshooting guidance directs users to add the Puppeteer Heroku buildpack through the app’s buildpack settings and to verify required Linux browser libraries. Because required libraries vary, inspect the deployed binary rather than copying an old list:

ldd /path/to/chrome | grep not

Any missing shared library must be addressed using configuration compatible with your current stack and browser package. A browser that starts locally but fails, falls back, or exits early on Heroku is not a pagination problem.

Use sandbox flags only as required by the deployment

Heroku guidance describes launching with --no-sandbox. Apply that setting only to the Heroku launch configuration where it is required, and keep the browser and dependency versions pinned during comparison.

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

Common symptoms and fixes

Symptom Likely cause Fix
Every page breaks earlier on Heroku Smaller usable area from paper size, margins, scale, or a different font Log all PDF options, computed print dimensions, and loaded fonts; set geometry explicitly.
Only headings or long paragraphs move Font substitution or a different weight/style face Ship the exact faces, verify their network requests and status, then wait for fonts.
Screen looks correct but PDF differs Print media rules Inspect @media print; use emulateMediaType('screen') only if screen styling is the intended PDF design.
PDF is blank or times out on Heroku Browser dependency failure, navigation timeout, blocked asset, or bot check Inspect browser logs, network failures, buildpack/libraries, and readiness selectors before investigating breaks.
CSS page size appears ignored preferCSSPageSize is false or API format wins Set preferCSSPageSize: true when CSS should control size, or remove competing CSS and set API dimensions.
Manual breaks work locally but not in production Underlying layout inputs differ Align fonts, geometry, media, and browser versions first; only then tune break-before, break-after, or break-inside.

Validate the fix and keep it stable

Compare layout, not just page count

A matching page count can hide a one-line shift that breaks a signature block later. Compare the first divergent page, element bounding boxes, computed font values, and the PDF’s paper dimensions. Store a known fixture and regenerate it after Puppeteer, browser, font, Heroku stack, or print-CSS changes.

Change one variable at a time

  • First align print versus screen media.
  • Then align font files, weights, and readiness.
  • Then align paper size, margins, CSS precedence, and scale.
  • Finally align browser/Puppeteer versions and Linux dependencies.

Only after those inputs match should you adjust manual page-break rules. A break rule cannot reliably compensate for changed glyph metrics or a different printable area.

Performance and reliability considerations

networkidle0 can wait indefinitely on applications with analytics or streaming requests; use a reliable application readiness selector and a bounded timeout. Loading every lazy image for a very long document increases memory and generation time, so test representative maximum-length inputs. Close the browser in a finally block, and capture browser console, request-failure, and process-exit logs in production.

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 of a URL rather than controlling a Heroku browser process, ScreenshotNeo provides a single HTTP endpoint. Its cleanup step accepts cookie/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 the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server for Claude, Cursor, and other MCP clients, with take_screenshot, get_page_info, and capture_pdf tools.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for output and option details. Equivalent calls are:

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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There is no browser setup to maintain, and the free plan includes 1,000 screenshots a month without a card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Should I force a page break after every section?

No. Forced breaks hide the cause and become unstable when content, fonts, or paper dimensions change. Use them only after the rendering inputs are aligned and a deliberate document-design rule requires them.

Does increasing the viewport fix PDF pagination?

Usually not. Viewport settings affect responsive layout, while PDF paper size, margins, scale, and print CSS determine the printed page. Set both deliberately when responsive breakpoints are part of the design.

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

Can a cache explain different breaks?

Yes, if one environment receives different HTML, CSS, font, or image bytes. Log asset URLs and response status, use the same fixture, and disable application-level content variation while diagnosing.

What evidence should I provide when escalating the issue?

Provide both PDFs, the smallest reproducible HTML/data fixture, exact PDF options, print CSS, loaded-font report, Puppeteer and browser versions, Heroku stack/buildpacks, and the first page where output diverges.

The Bottom Line

Consistent Heroku PDFs come from identical rendering inputs—not from piling on page-break declarations. Pin the browser stack, provide the same fonts, make print geometry explicit, wait for real readiness, and compare a reproducible fixture before changing layout rules.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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.

More from the Wire

  1. Shenzhen desk3 min
    HONOR Expands Beyond Smartphones With Humanoid Robot RevealHONOR said it unveiled its first humanoid robot at MWC 2026 and named shopping assistance, workplace inspections, and supportive companionship as intended uses. Later Robotics D1 claims and a reported…
  2. Cupertino desk5 min
    Apple Unveils AirPods Max 2: The Upgrade That Should Have Happened Years AgoAirPods Max 2 adds H2-powered audio features and Apple claims up to 1.5× more effective ANC, but its design, Smart Case, and 20-hour battery rating are unchanged. Wired lossless audio…
  3. Cupertino desk4 min
    Apple’s OLED Touch MacBooks Are Coming—but the Dynamic Island Is the Real GambleApple has not announced an OLED touchscreen MacBook, but reports point to high-end models arriving in late 2026 or early 2027. The reported Mac Dynamic Island could be useful, but…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.