ScreenshotNeo

BlogHTML to image & PDF

How to capture web pages as PDFs with Playwright and preserve page breaks

Generate PDFs with Playwright’s Chromium browser and use print CSS to control page size, margins, and page breaks without splitting compact elements.

By the ScreenshotNeo team4 October 20268 min read

Use Playwright’s Chromium browser to render a page as a PDF, then control pagination with print CSS. page.pdf() uses print media by default. Set paper size and margins deliberately, use break-before: page for intentional section starts, and apply break-inside: avoid selectively to compact elements that can fit on one page. Review the generated PDF in the browser version used for production: CSS break rules express layout preferences, not guarantees for impossible page geometry. See the Playwright PDF API and CSS paged media documentation.

1. Generate a PDF with Playwright

Install Playwright and its Chromium browser, save the following as pdf.js, then run it with Node.js. The example waits for network activity to settle, writes an A4 PDF, enables backgrounds, and sets matching CSS page-size preference and margins.

npm init -y
npm install playwright
npx playwright install chromium
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', {
      waitUntil: 'networkidle',
      timeout: 60000,
    });

    // Replace or supplement this with the application's own ready signal
    // when content renders asynchronously after network activity settles.
    await page.pdf({
      path: 'page.pdf',
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: {
        top: '18mm',
        right: '16mm',
        bottom: '18mm',
        left: '16mm',
      },
    });
  } finally {
    await browser.close();
  }
})();

Run node pdf.js. page.pdf() returns a PDF buffer; the path option also saves that buffer to a file. The documented PDF export is Chromium-only, so use Chromium for this step and install browser binaries through Playwright’s installation workflow. Keep the Playwright package and browser installation aligned when upgrading.

2. Control page breaks with print CSS

Put document-specific layout rules in @media print. Define paper dimensions and margins in @page when CSS should own them, and use break properties on elements that produce boxes in normal flow.

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

@media print {
  .start-new-page {
    break-before: page;
  }

  .finish-page-before-next {
    break-after: page;
  }

  /* Use on a figure, compact card, or small table group. */
  .keep-together {
    break-inside: avoid;
  }

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

  body {
    -webkit-print-color-adjust: exact;
  }
}

Apply the classes in markup where they reflect the document structure:

<section class="start-new-page">
  <h2>A new chapter</h2>
  <p>This section begins on a new page.</p>
</section>

<figure class="keep-together">
  <img src="chart.png" alt="Monthly totals">
  <figcaption>Monthly totals for the reporting period.</figcaption>
</figure>

Use natural pagination for flowing articles and reports. Add explicit page starts for chapters or document sections that truly need them. Keep-together rules work best on short units: a figure and caption, a compact card, or a small table group. A block taller than the printable page cannot remain intact; protecting a large section can cause awkward whitespace or still fail to keep its contents together. A break rule also has no effect if the selected element generates no box.

3. Choose paper, media, color, and PDF options

Choose one clear source of truth for page dimensions. With preferCSSPageSize: true, the CSS @page size takes priority over API format, width, and height. Otherwise, the API paper setting takes precedence. Avoid contradictory CSS and API dimensions.

Need Setting Practical note
Standard paper format: 'A4' or another supported format Use a named paper format when it suits the target document.
Custom dimensions width and height Specify units such as mm, in, or px and decide whether CSS or API dimensions take precedence.
CSS-defined paper preferCSSPageSize: true Lets @page size govern the output rather than API paper dimensions.
Whitespace around content margin Set top, right, bottom, and left values; avoid accidentally combining competing CSS and API margin rules.
Page orientation landscape: true Useful for wide tables and charts; confirm the page-break behavior at the new width.
Background fills and images printBackground: true PDF output omits backgrounds unless requested.
Scale scale Scaling changes fit and pagination; adjust only after checking legibility and page count.
Print versus screen layout page.emulateMedia({ media: 'screen' }) Print media is the default. Use screen media only when intentionally exporting screen styles.
Headers and footers displayHeaderFooter, headerTemplate, footerTemplate Templates support injected date, title, URL, page number, and total-page classes. Template scripts are not evaluated, and page styles do not apply inside the templates.

Print output can adjust authored colors. Enable printBackground for background rendering and use -webkit-print-color-adjust: exact where preserving authored colors matters. Inspect the exported file because the result depends on actual page styles and browser rendering.

4. Wait for the page to be ready

networkidle is a useful navigation condition, but it does not prove that every application has finished rendering. A page may load data or draw charts after network activity settles. If the site exposes a ready marker, wait for it before exporting:

await page.goto('https://example.com/report', {
  waitUntil: 'domcontentloaded',
  timeout: 60000,
});
await page.locator('[data-report-ready="true"]').waitFor({ timeout: 30000 });
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });

Replace the selector with a signal the application actually sets. Other useful choices include waiting for a known element to appear, waiting for a specific response, or using a deliberate short delay if the page offers no readiness signal. A fixed delay is simple but can be wasteful on fast runs and too short on slow ones. Avoid treating one wait strategy as universal.

5. Review and refine the generated PDF

  1. Open the PDF generated by the same Chromium and Playwright versions used in the target runtime.
  2. Check page count, paper dimensions, margins, and orientation.
  3. Look for headings stranded at page bottoms, clipped content, split figures, and oversized blocks.
  4. Inspect large whitespace. Narrow or remove keep-together rules that force content away from available space.
  5. Check background colors, images, fonts, and any header or footer templates.
  6. Change one layout rule at a time and regenerate the PDF, especially after content or font changes.

Exact page composition can vary as content length, fonts, paper size, and margins change. CSS break preferences improve pagination, but a protected unit must fit inside the printable area to stay whole.

6. Troubleshooting

Symptom Likely cause Fix
The PDF looks like print mode rather than the browser screen. page.pdf() uses print media by default. Add print-specific CSS. If screen styling is explicitly required, call await page.emulateMedia({ media: 'screen' }) before page.pdf().
A section does not start on a new page. The break rule is on the wrong element, or the element generates no box. Put break-before: page on the section wrapper in normal flow and confirm it has rendered content.
A figure or card splits across pages. No avoidance rule is applied, or the protected unit is taller than the printable area. Apply break-inside: avoid to a compact wrapper. Reduce its size or let it fragment if it cannot fit.
The PDF has large gaps or sections begin on awkward pages. Keep-together rules are too broad, or forced breaks no longer fit the changing content. Remove broad rules, protect only small units, and reserve forced starts for genuine section boundaries.
CSS paper dimensions appear ignored. API paper options take precedence because CSS page-size preference is off, or the values conflict. Set preferCSSPageSize: true when CSS should control size; otherwise set API format or dimensions deliberately.
Background colors are missing or altered. Background printing is off or print color adjustment changed the authored colors. Set printBackground: true and, where needed, -webkit-print-color-adjust: exact; inspect the actual export.
Some content is missing or stale. Export started before application-specific rendering completed. Wait for an app-ready selector or other concrete signal before calling page.pdf().
PDF output changes after an upgrade. The Playwright or Chromium version changed rendering behavior. Install the browser using Playwright’s browser workflow, record both versions when diagnosing, and review the new output.

7. Performance, reliability, and cost

PDF generation includes browser startup, page navigation, application rendering, and print layout. Reuse a browser process for multiple documents in a controlled worker when appropriate, while creating an isolated page or context per job so one document’s state does not leak into another. Close pages and browsers reliably. Set explicit navigation and readiness timeouts, and capture errors with the URL and browser versions to make failures diagnosable.

Network idle can take a long time on pages with persistent requests, and it can still miss delayed application rendering. Prefer the narrowest reliable readiness condition for the page. For large or image-heavy documents, account for browser memory and output size, and avoid unnecessary full-page assets or repeated browser launches. Playwright itself is software; the relevant operating cost depends on the infrastructure and workload where Chromium runs. This approach does not require a physical product.

8. Or skip the browser setup

ScreenshotNeo can capture a URL as a PDF with one API request. It handles the browser setup and offers PDF options such as paper size, margins, landscape, and page ranges. The DIY method above is the right fit when you need to control your own browser and print CSS. For a URL-based capture, you can use the ScreenshotNeo API documentation and call:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -d format=pdf \
  -o page.pdf
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com",
        "format": "pdf",
    },
    timeout=90,
)
r.raise_for_status()
open("page.pdf", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
  format: 'pdf',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
await require('node:fs/promises').writeFile('page.pdf', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Sign up for free and get 1,000 screenshots a month with no card.

9. FAQ

Can Playwright preserve an exact page layout?

It can express page-break preferences and page dimensions, but output depends on content, fonts, margins, and Chromium rendering. Generate and inspect the PDF for the target runtime.

Should every heading start on a new page?

No. Let ordinary articles flow naturally. Use explicit page starts for major divisions that need them, and discourage a heading from separating from its following content with a selective break-after: avoid-page rule.

Can I use Firefox or WebKit for this PDF export?

The documented Playwright PDF export path is Chromium-only. Use Chromium to generate the PDF.

Does break-inside: avoid guarantee a figure stays together?

No. It is an avoidance preference. The whole figure and caption still need to fit within the printable page area.