Fonts
Font handling is one of the most common sources of rendering inconsistency in PDF generation. This page explains how Press.js manages fonts and how to ensure your document looks correct everywhere.
The problem: system fonts are not portable
Section titled “The problem: system fonts are not portable”Press.js renders your document in Chromium, which has access to the full font library of the machine it runs on. Your development machine has one set of fonts, your CI server has another, and the Press.js Cloud renderer may have yet another. If your document references a font by name without providing a font file, the rendering result depends on whatever happens to be installed on that specific machine.
A document that uses “Helvetica Neue” will look correct on macOS (where it is installed), silently fall back to “Helvetica” on some Linux systems, and use a different fallback entirely on Windows. The PDF will look different depending on where it was rendered.
The rule is simple: never rely on system fonts. Every font used in your document must be explicitly declared with @font-face and hosted as a font asset.
Declaring fonts with @font-face
Section titled “Declaring fonts with @font-face”Define your fonts using standard CSS @font-face rules pointing to hosted font files:
@font-face { font-family: "Inter"; src: url("/fonts/inter-var.woff2") format("woff2"); font-weight: 400 700; font-display: block;}
@font-face { font-family: "Inter"; src: url("/fonts/inter-italic.woff2") format("woff2"); font-style: italic; font-weight: 400 700; font-display: block;}Once declared, reference them in your styles:
body { font-family: "Inter", sans-serif;}The generic fallback (sans-serif) will never be used in practice — it exists only to satisfy the CSS cascade.
What gets embedded in the PDF
Section titled “What gets embedded in the PDF”When Press.js generates a PDF, it embeds only the glyphs actually used in the document — not the entire font file. This means:
- A 200-page document using a single weight of Inter will produce a smaller PDF than expected, because only the used characters are embedded.
- Adding more font weights or styles increases PDF size because additional glyph sets must be embedded.
- Variable fonts (one file containing multiple weights) are efficient: a single
woff2can coverfont-weight: 400 700without duplicating glyph data.
How @press-js/fonts helps
Section titled “How @press-js/fonts helps”The @press-js/fonts package audits your document’s font usage and catches problems before they reach the PDF. It runs in both preview and deploy flows.
When you run the audit, it:
Detects unmanaged font families — fonts referenced in CSS that have no corresponding @font-face rule with a url() source. These fonts depend on system availability:
Font stack "Helvetica Neue" falls back to generic family "sans-serif"before Press.js can verify a managed font asset.Detects local-only font sources — fonts declared with local() but no url() source. These will work on machines that have the font installed but silently fall back elsewhere:
Font stack "My Font" depends on local() font family "My Font",so Preview and Cloud rendering may differ across machines.Detects missing font assets — @font-face rules whose font files cannot be found at the specified URL. These are reported as errors and block the render:
Font family "Inter" references missing asset "/fonts/inter-var.woff2".Identifies inaccessible stylesheets — stylesheets loaded from external domains that cannot be inspected for @font-face rules.
Using the audit
Section titled “Using the audit”The audit is integrated into the Press.js preview and CLI workflows. When font issues are detected, you will see warnings in the console and a font audit summary in the preview UI. Errors (such as missing font assets) will prevent the PDF from being generated.
Best practices
Section titled “Best practices”Prefer self-hosted fonts. Using Google Fonts CDN or other third-party hosts generally works, but network instability at capture time can cause fonts to fail to load. Self-hosting font files in your project’s asset directory eliminates this risk.
Prefer woff2 format. It offers the best compression and is universally supported by Chromium. Avoid TTF, OTF, and especially EOT formats.
Use variable fonts where possible. A single variable font file replaces multiple weight-specific files, reducing both project complexity and PDF size.
Keep font families small. Each additional font weight or style adds to the PDF’s embedded font data. Two weights (regular + bold) with matching italics is sufficient for most documents.
Use font-display: block. This ensures the renderer waits for the font to load before painting text, avoiding a flash of invisible text (FOIT) or a flash of fallback text (FOUT) during capture.
Do not rely on local() sources. The local() function in @font-face tells the browser to use a system-installed font if available. This is precisely the behavior that causes inconsistent rendering across machines. Always provide a url() source.