You have chosen an HTML-to-PDF API and written your first template — but the output does not look quite right. Text is clipped at page boundaries, images are missing, fonts are wrong, and the layout breaks on multi-page documents. Sound familiar? This guide covers the battle-tested best practices that will help you produce pixel-perfect PDFs from HTML every time.
Use Print-Friendly CSS
The browser has two rendering modes: screen and print. When generating PDFs, the rendering engine switches to print mode, which means your @media print rules take effect. This is your most powerful tool for controlling PDF output.
/* Base styles apply to both screen and print */
body {
font-family: 'Inter', sans-serif;
color: #333;
line-height: 1.6;
}
/* Print-specific overrides */
@media print {
/* Remove navigation, sidebars, and other UI elements */
nav, .sidebar, .footer, .no-print { display: none; }
/* Ensure backgrounds are printed */
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
/* Set page margins */
@page { margin: 20mm 15mm; }
/* Improve typography for print */
body { font-size: 11pt; line-height: 1.5; }
h1 { font-size: 22pt; }
h2 { font-size: 16pt; }
}
Key points:
- Always include
print-color-adjust: exact(and the-webkit-prefix) if you want background colors and images to appear in the PDF. By default, browsers strip backgrounds in print mode. - Use the
@pagerule to control page margins. This is separate from your body margins and controls the actual printable area. - Use physical units like
pt,mm, andcmfor print — they are more predictable thanpxorremin a paginated context.
Handle Page Breaks Properly
Page breaks are the most common source of PDF rendering frustration. Without proper break rules, Chromium will split content wherever it runs out of space — often in the middle of a table row, a heading, or a code block.
/* Prevent headings from being orphaned at the bottom of a page */
h1, h2, h3, h4 {
page-break-after: avoid;
break-after: avoid;
}
/* Keep images and figures together */
figure, img {
page-break-inside: avoid;
break-inside: avoid;
}
/* Prevent table rows from splitting across pages */
tr {
page-break-inside: avoid;
break-inside: avoid;
}
/* Repeat table headers on each page */
thead {
display: table-header-group;
}
/* Force a page break before a new section */
.new-section {
page-break-before: always;
break-before: page;
}
/* Avoid widows and orphans */
p {
widows: 3;
orphans: 3;
}
The widows and orphans properties control how many lines of a paragraph must appear at the top or bottom of a page. Setting both to 3 ensures you never have a lonely line at the start or end of a page.
Web Fonts: Ensuring They Render
Fonts are a frequent source of PDF rendering issues. If a font fails to load, the browser falls back to a default system font, and your carefully designed template looks wrong. Here are the rules:
Use Google Fonts via Link Tag
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
Google Fonts are hosted on a fast CDN and work reliably with most PDF APIs. The display=swap parameter ensures the font is downloaded promptly.
Self-Host Critical Fonts
For maximum reliability, self-host your fonts using @font-face declarations with absolute URLs:
@font-face {
font-family: 'CustomFont';
src: url('https://yourdomain.com/fonts/CustomFont-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
Always Specify Fallback Fonts
body {
font-family: 'Inter', 'Helvetica Neue', Arial, sans-serif;
}
If your primary font fails to load, the fallback ensures the document is still readable.
Wait for Font Loading
If you are using a PDF API that supports JavaScript execution, you can wait for fonts to load before rendering:
await document.fonts.ready;
// Signal to the API that the page is ready to render
Images: Use Absolute URLs
This is a simple rule that prevents countless issues: always use absolute URLs for images. When the PDF API renders your HTML, it needs to fetch images from the network. Relative URLs like ./images/logo.png will fail because the API server does not have access to your local file system.
<!-- Wrong: relative URL -->
<img src="./images/logo.png" />
<img src="/images/logo.png" />
<!-- Correct: absolute URL -->
<img src="https://yourcompany.com/images/logo.png" />
For images that are not publicly accessible, you have two options:
- Inline as base64 — Encode the image as a data URI:
<img src="data:image/png;base64,iVBOR..." />. This works but increases the HTML size significantly. - Presigned URLs — Generate a temporary URL from your storage provider (S3, GCS) that grants read access for a limited time.
Headers, Footers, and Page Numbers
Headers and footers are rendered outside the main page content area. Most PDF APIs (including TongoRender) let you define them as separate HTML templates:
{
headerTemplate: `
<div style="font-size: 9px; color: #999; width: 100%; text-align: center; padding: 5px 0;">
CONFIDENTIAL — Acme Corp
</div>
`,
footerTemplate: `
<div style="font-size: 9px; color: #999; width: 100%; display: flex; justify-content: space-between; padding: 5px 20px;">
<span>Generated on 2026-03-01</span>
<span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
`,
displayHeaderFooter: true,
margin: { top: '30mm', bottom: '25mm', left: '15mm', right: '15mm' }
}
Important notes about headers and footers:
- The
.pageNumberand.totalPagesCSS classes are special — Chromium automatically replaces their content with the current and total page numbers. - Headers and footers render inside the margin area, so make sure your margins are large enough to accommodate them.
- Header and footer templates are rendered in an isolated context — they cannot access styles from the main document. Include all styles inline.
- Use small font sizes (8-10px) for headers and footers to keep them unobtrusive.
Testing and Debugging Tips
Use Chrome DevTools Print Preview
Before sending HTML to an API, test it locally using Chrome's print emulation. Open DevTools, press Cmd/Ctrl+Shift+P, type "Rendering", and enable "Emulate CSS media type: print". This shows you exactly how your CSS print styles will be applied.
Add Visible Borders During Development
/* Debug: show element boundaries */
* { outline: 1px solid rgba(255, 0, 0, 0.2); }
This makes it easy to see how elements are sized and positioned in the PDF layout.
Test with Different Content Lengths
Your template might look great with one page of content but break with three pages. Test with:
- Minimal content (half a page)
- Typical content (2-3 pages)
- Maximum content (10+ pages with long tables)
Check Right-to-Left (RTL) Support
If you serve users in Arabic, Hebrew, or other RTL languages, test your templates with dir="rtl" on the HTML element.
Validate with Real Data
Templates often break with edge-case data: very long company names, addresses with special characters, line items with extremely long descriptions. Test with realistic data, not just placeholder text.
Conclusion
Generating PDFs from HTML is straightforward once you understand the rules of the print rendering context. Use print CSS, handle page breaks explicitly, serve fonts reliably, use absolute image URLs, and test thoroughly with varied content. These practices will save you hours of debugging and ensure your PDFs look professional every time.
TongoRender handles the rendering infrastructure so you can focus on crafting great templates. With full Chromium rendering, modern CSS support, and configurable headers and footers, your HTML-to-PDF workflow has never been simpler.
Get started with TongoRender — 100 free renders per month, no credit card required.