Repeat a header on every page of a PDF
If you arrived here from an error like
Chromium failed to print the PDF; this usually happens when the page is too large
you were probably trying to put your document's header into Chromium's
headerTemplate option, and you inlined your stylesheet to make it look right.
That error is the second wall. There are two, and both are closed.
Why headerTemplate cannot be styled
Chromium renders headerTemplate and footerTemplate in an isolated context that
cannot reach the page's stylesheet. Whatever your build pipeline injects — Vite,
Tailwind, a linked CSS file — never arrives, so the header renders as unstyled text.
The obvious fix is to inline the built CSS into the header template itself. That is what produces the error above: the header template has a size limit, and a real stylesheet blows past it. Gotenberg's maintainer has closed this as unfixable, because it is Chromium's behaviour rather than a wrapper's bug.
Renderpaper has no header or footer options at all, so this route does not exist here either. What follows works in any Chromium-based renderer, including this one.
What does not work: position: fixed
This is everybody's first guess, so it is worth recording precisely what it does.
.fixed-header { position: fixed; top: 0; left: 0; right: 0; }
.fixed-footer { position: fixed; bottom: -11mm; left: 0; right: 0; }
Rendered against a 90-row table across three A4 pages, then measured with
pdftotext -bbox and looked at:
| page 1 | page 2 | page 3 | |
|---|---|---|---|
| header | y 32.4–55.8 | y 32.4–55.8 | y 32.4–55.8 |
| first content row | y 36.1 | y 36.9 | y 36.9 |
| footer | absent | y 51.8 — the top of the page | y 51.8 |
The header does repeat. That is not the problem. The problem is that a fixed element
is out of normal flow, so it paints over the content instead of pushing it
down: on every page the header sits on top of the first table row, and both are
unreadable. The footer is worse — it never appears on page one, and on later pages
bottom resolves against the wrong box, landing it at the top of the page across
the rows.
A screenshot makes it obvious in a way coordinates do not: page two has "Statement 2026-0142" and "Line item 036" printed on top of each other.
What works: table-header-group
Wrap the document in an outer table. Put the repeating header in <thead> and the
repeating footer in <tfoot>.
<table>
<thead>
<tr><td colspan="3" class="docheader">
<strong>Statement 2026-0142</strong> — Northwind Trading AB
</td></tr>
</thead>
<tfoot>
<tr><td colspan="3" class="docfooter">
Payment within 30 days · Reference 2026-0142
</td></tr>
</tfoot>
<tbody>
<tr><td colspan="3">
<!-- the whole document body goes here -->
</td></tr>
</tbody>
</table>
thead { display: table-header-group; }
tfoot { display: table-footer-group; }
tr { break-inside: avoid; }
Three things follow from being in normal flow, and they are the whole reason this works:
- The repeated header pushes content down rather than painting over it.
- Your own stylesheet applies, because this is part of the document. Tailwind, web fonts, CSS variables — all of it behaves normally.
- There is no size ceiling, because there is no header template to overflow.
Measured on the same 90-row document:
| page 1 | page 2 | page 3 | |
|---|---|---|---|
| header | y 32.4–55.8 | y 32.4–55.8 | y 32.4–55.8 |
| first content row | y 59.4 | y 55.6 | y 55.6 |
| footer | y 783.0 | y 783.0 | y 583.5 |
No overlap on any page. The header repeats, the content starts below it, and the footer sits at the foot.
The caveat, stated plainly
Look at the footer on page three: y 583 rather than 783. On the final page the
tfoot sits immediately after the last row rather than pinned to the bottom of the
sheet, so there is visible white space beneath it.
This is inherent to table-footer-group and there is no CSS fix. If a
bottom-anchored final footer matters more than a repeating one, this technique is
the wrong choice.
Page numbers are a separate problem with a better answer
If what you actually want in the repeating header is a page number, thead cannot
give you one — it is the same content on every page. Chromium does support CSS
margin boxes, which can:
@page {
@bottom-center { content: "Page " counter(page) " of " counter(pages); }
}
That is written up in page numbers in a PDF, and it
composes with this technique — a repeating thead header plus a margin-box page
number in the same document.
Verification
Both variants were rendered through Renderpaper on 2026-08-08 at 90 rows and A4,
producing three pages each. Coordinates come from pdftotext -bbox; both page two
renders were rasterised and inspected. The failure table above is measurement, not
recollection.