ScreenshotNeo

BlogHTML to image & PDF

Printing PDFs with Puppeteer, Playwright, and Flexbox Layouts

Generate predictable PDFs with Puppeteer or Playwright, control print CSS, and diagnose Flexbox page-break problems.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: both Puppeteer and Playwright generate a PDF with page.pdf(). They use print CSS media by default, so define your print layout with @media print and @page. If the PDF should look like the screen, explicitly emulate screen media before calling page.pdf(). Flexbox can fragment across pages according to flex-specific rules, so treat break-before, break-after, and break-inside as layout intent and inspect the rendered PDF in the browser version you deploy.

How PDF printing works

The high-level workflow is the same in both libraries:

  1. Launch a browser.
  2. Create a page.
  3. Navigate to the document and wait for the data and assets your application needs.
  4. Choose print or screen media.
  5. Call page.pdf() with deliberate paper and pagination options.
  6. Close the browser.

Puppeteer’s guide summarizes the operation as “For printing PDFs use Page.pdf().” PDF generation waits for fonts by default in Puppeteer, but you still need to ensure that your own asynchronous data, images, and components are ready before export. The documentation does not guarantee that every network dependency has loaded.

Minimal Puppeteer example

The following Node.js script creates a PDF using print CSS. See the Puppeteer page.pdf() reference and PDF generation guide for the API details.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com/report', { waitUntil: 'networkidle0' });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
  });
} finally {
  await browser.close();
}

Use screen styles intentionally

Print media is the default. To make the PDF use your screen rules, call Puppeteer’s media emulation method before exporting:

await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-styled.pdf', printBackground: true });

Use this only when the screen composition is the intended document. Screen layouts often contain navigation, hover states, fixed toolbars, or colors that are unsuitable for paper.

Minimal Playwright example

Playwright exposes the same page.pdf() model. Consult the Playwright Page API.

import { chromium } from 'playwright';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com/report', { waitUntil: 'networkidle' });
  await page.pdf({
    path: 'report.pdf',
    format: 'A4',
    printBackground: true,
    margin: { top: '16mm', right: '14mm', bottom: '16mm', left: '14mm' }
  });
} finally {
  await browser.close();
}

To select screen media in Playwright, use:

await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'screen-styled.pdf', printBackground: true });

PDF options that affect output

Decision What to set Why it matters
Paper format, or explicit width and height Controls the available page box. Letter is the documented default in both APIs; specify the format for user-facing output.
Orientation landscape: true Useful for wide tables and dashboards.
Margins margin: { top, right, bottom, left } Reserve printable space and prevent content from touching the edge.
Scale scale Changes the rendered size. Recheck wrapping and page count after changing it.
Backgrounds printBackground: true Background graphics are off by default in the documented options.
CSS page size preferCSSPageSize: true where supported Lets CSS @page sizing take priority instead of silently relying on the API paper size.
Headers and footers API header/footer templates where available Add page numbers or document metadata without putting those elements in the document flow.
Page ranges pageRanges Export selected pages for previews or partial reports.

PDF printing can modify colors. Add this print rule when exact color reproduction is important:

@media print {
  * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

Color adjustment affects color rendering; it does not fix layout, overflow, or pagination.

Use @page for page-level settings and @media print for document styling. CSS Paged Media defines page size, orientation, margins, and generated page features; browser support can differ because the cited specification is a Working Draft.

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

@media print {
  nav,
  .toolbar,
  .chat-widget {
    display: none;
  }

  .report {
    color: #111;
    background: white;
  }

  h2 {
    break-before: page;
  }

  .card,
  figure,
  table {
    break-inside: avoid;
  }

  .chapter {
    break-after: page;
  }
}

Modern fragmentation properties are break-before, break-after, and break-inside. The older page-break-* properties may still appear in existing stylesheets, but use the modern names for new rules. orphans and widows can influence line breaks in block text. No rule can keep content together when that content is taller than a page.

Why Flexbox layouts break across PDF pages

Flexbox is a paged layout participant, not an indivisible image. The CSS Fragmentation specification gives layout modes such as flex their own fragmentation rules. Breaks can occur between flex items, between lines in a multi-line flex container, and inside items when their break properties allow it. A directive that works in ordinary block flow therefore may produce a different result inside a flex container.

Common causes include:

  • A row of cards is wider than the printable page, so items shrink or overflow.
  • A fixed height, min-height, or viewport unit leaves insufficient room for a break.
  • flex-wrap creates lines whose boundaries do not match your intended page boundaries.
  • break-inside: avoid is applied to a group taller than one page, making the browser choose an unavoidable overflow or an unexpected break.
  • Screen-only positioning, sticky elements, or transforms change the print geometry.
  • The browser version implements flex fragmentation differently from the version used during development.

A practical Flexbox pattern

.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.card {
  flex: 1 1 240px;
  break-inside: avoid;
}

@media print {
  .cards {
    display: block;
  }

  .card {
    margin-bottom: 12mm;
  }

  .card + .card {
    break-before: auto;
  }
}

A print-only block layout can make pagination easier to reason about, but it is not universally required. First try explicit sizing, sensible wrapping, and fragmentation rules. If the design still produces unacceptable pages, a print-specific layout is a valid implementation choice.

Debug pagination in the target browser

  1. Save the exact HTML, CSS, data, and browser version used in production.
  2. Open the page with print media emulation and inspect the computed sizes.
  3. Temporarily add outlines to flex containers and items.
  4. Remove fixed heights, transforms, and viewport-based dimensions one at a time.
  5. Test with short, average, and very long content.
  6. Render the PDF and inspect every boundary, especially tables, cards, headings, and images.

Specifications describe intent, while implementations and browser releases can differ. Verify the actual PDF instead of assuming a break directive guarantees a particular page.

Loading data, fonts, and images before export

Navigation completion is only one readiness signal. Wait for your application state and critical selectors explicitly:

await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready]');
await page.evaluate(() => document.fonts.ready);
await page.evaluate(() => {
  for (const image of document.images) {
    if (!image.complete) image.loading = 'eager';
  }
});

Use a bounded timeout around application-specific waits. A page that never sets its ready marker should fail clearly instead of holding a worker indefinitely.

Troubleshooting

Symptom Likely cause Fix
PDF looks different from the page Print media is the default Add emulateMediaType('screen') in Puppeteer or emulateMedia({ media: 'screen' }) in Playwright, or write dedicated print CSS.
Colors or backgrounds are missing Background printing is disabled or colors are adjusted Set printBackground: true and use -webkit-print-color-adjust: exact when needed.
Cards split unexpectedly Flex fragmentation, fixed sizing, or an overlarge item Inspect the target browser, remove rigid heights, set deliberate break-inside rules, and test a print-only block layout.
Content is cut off Overflow, transforms, or a container taller than the page Remove print-time overflow restrictions, check margins and scale, and allow the content to fragment.
Images or fonts are absent Assets were not ready when the PDF was created Wait for the application-ready selector, document.fonts.ready, and critical image completion.
Wrong paper dimensions Implicit Letter default or competing @page rules Set format or dimensions explicitly and decide whether CSS page size should take priority.
Export hangs Network-idle never occurs or a page request remains open Use a bounded wait, wait for a specific readiness condition, and log the URL and failing resource.

Puppeteer or Playwright?

Both libraries expose PDF generation and default to print media. Choose the library already integrated with your project, the language and runtime conventions you use, and the exact options or header/footer behavior required. The cited documentation establishes overlapping capabilities; it does not provide a fair performance, reliability, or browser-matrix ranking. Puppeteer’s referenced API documentation displayed version 25.12.0, while the Playwright reference was not release-pinned, so pin and verify the browser version in your own deployment.

Performance, reliability, and cost considerations

  • Reuse a browser process when safe, while isolating pages and closing them after each job.
  • Wait for application readiness instead of an unnecessarily long fixed delay.
  • Reduce third-party requests and disable animations in print CSS to make output more deterministic.
  • Set explicit timeouts and capture logs for navigation, readiness, and PDF creation.
  • Keep a small set of representative fixtures: short content, long paragraphs, large tables, missing images, and a multi-line flex layout.
  • PDF size grows with images, embedded fonts, and background graphics. Optimize those inputs when transfer or storage cost matters.

Or skip the browser setup

ScreenshotNeo provides a website capture API and PDF endpoint when you do not want to operate a browser worker. See the ScreenshotNeo API documentation for options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);

Before capture, cookie and consent banners, newsletter popups, and chat widgets are removed. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does page.pdf() use screen CSS?

No. Print media is the default. Emulate screen media explicitly when that is the intended result.

Can CSS guarantee that a Flexbox card never splits?

No. break-inside: avoid communicates intent, but an item taller than a page cannot remain intact and flex fragmentation has layout-specific rules.

Should I always replace Flexbox with block layout for PDFs?

No. Test the flex layout first. Use a print-only block or grid arrangement when it produces clearer, more maintainable pagination for your document.

Why does changing scale alter page breaks?

Scale changes the rendered geometry, including line wrapping and available content per page. Recheck every boundary after changing it.

Where should page size be defined?

Define the API paper option deliberately and use @page for CSS page rules. If CSS dimensions must win, enable the API’s CSS-page-size preference where available.