How to Remove the Extra Blank Last Page in wkhtmltopdf
Remove wkhtmltopdf’s extra blank page by fixing trailing page breaks, overflow, margins, and layout edge cases.
Usually, the extra page comes from a forced break after the final page. Remove the trailing page-break-after: always, break-after: page, empty break element, or generated pseudo-element. Apply a forced break only between page wrappers:
.page:not(:last-child) {
page-break-after: always;
break-after: page;
}
.page:last-child {
page-break-after: auto;
break-after: auto;
}
If the blank page remains, the final wrapper is probably slightly taller or wider than the printable area. Margins, padding, borders, percentage dimensions, image rounding, floats, and A4 landscape layout can all create a small overflow. Fix the box model and wkhtmltopdf margins before using a small reduction such as 99.9% as a diagnostic.
1. Remove the trailing forced page break
A declaration such as page-break-after: always means “finish this page and start another one.” When it is attached to the last visible block, wkhtmltopdf can create a new page containing no content. The same applies to modern break-after: page, an empty element with a break rule, or a generated ::after element.
Use page wrappers and scope the break to every wrapper except the last:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
@page {
size: A4;
margin: 12mm;
}
html, body {
margin: 0;
padding: 0;
}
*, *::before, *::after {
box-sizing: border-box;
}
.page {
min-height: 273mm;
page-break-inside: avoid;
}
.page:not(:last-child) {
page-break-after: always;
}
.page:last-child {
page-break-after: auto;
}
</style>
</head>
<body>
<section class="page">
<h1>First page</h1>
<p>Content for page one.</p>
</section>
<section class="page">
<h1>Final page</h1>
<p>This page does not request another page after it.</p>
</section>
</body>
</html>
Also remove trailing markup such as <div class="page-break"></div>, <br class="page-break">, and rules like:
.page::after {
content: "";
display: block;
page-break-after: always;
}
2. Check the final page’s actual dimensions
After removing the break, inspect the final wrapper and its ancestors. A fixed-size element can overflow by a fraction when its declared dimensions are combined with padding or borders.
For a printable A4 page, the usable height is the paper height minus the top and bottom margins. If the wrapper has height: 100%, padding, and a border under the default content-box model, its outer height can exceed that usable height.
html, body, .page {
margin: 0;
padding: 0;
}
.page {
box-sizing: border-box;
width: 210mm;
min-height: 297mm;
border: 1px solid #ddd;
padding: 12mm;
}
When wkhtmltopdf margins are set on the command line, do not also assume that a 100% wrapper fills the entire physical sheet. The wrapper is laid out inside the printable region created by those margins.
Diagnostic test for fractional overflow
Temporarily remove width: 100% and height: 100% from the final wrapper. If the blank page disappears, you have confirmed a sizing boundary. A documented A4 landscape case was also fixed temporarily by changing both dimensions to 99.9%:
.page {
width: 99.9%;
height: 99.9%;
}
Use this only to confirm the cause. The durable fix is to make the page dimensions, margins, padding, borders, and box sizing agree precisely.
3. Use a controlled wkhtmltopdf command
Keep the rendering options explicit while debugging so that a change in page size, orientation, DPI, or margins does not hide the real cause.
wkhtmltopdf \
--page-size A4 \
--orientation Portrait \
--margin-top 12mm \
--margin-right 12mm \
--margin-bottom 12mm \
--margin-left 12mm \
--encoding utf-8 \
input.html output.pdf
For landscape, state it explicitly and retest the same fixture:
wkhtmltopdf \
--page-size A4 \
--orientation Landscape \
--margin-top 10mm \
--margin-right 10mm \
--margin-bottom 10mm \
--margin-left 10mm \
input.html output-landscape.pdf
Make sure the HTML used for the test is the same file and the same wkhtmltopdf binary used in production. Reports of this problem have involved older builds including 0.12.2.1 and 0.12.5-1, and behavior can vary with the patched Qt build and operating system.
4. Investigate layout features that affect pagination
Floated parents
Page-break rules can behave unexpectedly inside a floated parent. Move pagination-critical sections out of a float and use a normal block or a layout container that wkhtmltopdf handles consistently.
/* Prefer this for page-level wrappers */
.document {
display: block;
}
.page {
clear: both;
page-break-inside: avoid;
}
Tables
Tables can create overflow when a row, border, or repeated header is pushed across a page. Avoid putting an entire page wrapper inside a table. Apply page-break-inside: avoid selectively to rows or smaller groups, then verify that content still fits.
Images and replaced elements
An image whose intrinsic dimensions are larger than expected can add a few pixels to the final page. Set a bounded width, preserve its aspect ratio, and wait until it is loaded before generating the PDF:
img {
display: block;
max-width: 100%;
height: auto;
}
figure {
page-break-inside: avoid;
}
Absolute positioning
Absolutely positioned content can extend beyond the normal flow without increasing the apparent height of its parent. Check bottom offsets, transforms, and fixed footers near the document end.
Generated content and empty nodes
Inspect ::before and ::after rules, trailing <br> elements, and empty containers. A generated block with a break rule is still a request for another page.
5. A repeatable troubleshooting sequence
- Inspect the last visible element and every ancestor for
page-break-after: always,break-after: page, generated content, empty break nodes, and trailing margins. - Change the break rule to
.page:not(:last-child)and explicitly set the last page toauto. - Temporarily set
html, body, .page { margin: 0; padding: 0; }and applybox-sizing: border-boxto fixed-size wrappers. - Confirm all four wkhtmltopdf margins and the page size match the CSS assumptions.
- Remove
width: 100%andheight: 100%from the final wrapper. If that fixes it, measure the box and correct the overflow. - Check floats, tables, images, transforms, and absolutely positioned content near the end.
- Reduce a test wrapper to
99.9%only to confirm fractional overflow, then replace the workaround with precise dimensions. - Re-run a minimal HTML fixture using the exact wkhtmltopdf version, orientation, DPI, margins, headers, and footers used in production.
6. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Blank page appears after every document | A break is applied to the final wrapper or a trailing empty node. | Scope the break to :not(:last-child) and remove the empty node. |
| Only A4 landscape adds a blank page | The full-size wrapper plus margins or borders crosses the printable boundary. | Use border-box, correct dimensions and margins, and test without 100% sizing. |
| Removing the break merges two intended pages | The break was needed between wrappers. | Keep the break on intermediate wrappers and remove it only from the last one. |
| CSS change has no effect | The wrong stylesheet or cached/generated HTML is being rendered. | Render a minimal file, inspect the generated HTML, and verify the binary and input path. |
| Break works outside a component but not inside it | A floated parent or complex table layout changes pagination. | Move page-level wrappers into normal block flow and simplify the fixture. |
| Footer pushes a new page | Header/footer spacing or bottom margins reduce usable height. | Set header/footer options and margins explicitly, then recalculate the wrapper height. |
7. Why wkhtmltopdf can expose this problem
wkhtmltopdf uses an older WebKit pagination model. The Debian manual describes the current WebKit page-breaking algorithm as leaving much to be desired: WebKit lays out a long page and then cuts it into pages, while the patched Qt behavior can only partially remedy difficult breaks. This is why a layout that looks correct in a browser can still produce a final empty sheet in wkhtmltopdf. See the wkhtmltopdf manual for the renderer’s documented options.
The CSS Paged Media model expects user agents to avoid content-empty pages, but older engines do not guarantee ideal results for every combination of floats, tables, fixed dimensions, and forced breaks. A minimal reproduction is therefore part of the fix, not just a reporting detail.
8. Performance, reliability, and cost considerations
- Performance: Smaller HTML and fewer layout-heavy tables reduce pagination work. Avoid repeatedly rendering a large document while debugging; isolate the final two page wrappers first.
- Reliability: Pin the wkhtmltopdf version and command-line options in production. Changes in patched Qt, operating system, page orientation, or margins can change pagination.
- Repeatability: Wait for required images and fonts, use deterministic dimensions, and keep external resources stable. Missing assets can change the final page height.
- Cost: Local wkhtmltopdf has no per-page API charge, but rendering consumes CPU and memory. A hosted renderer can trade infrastructure work for request pricing, so compare the volume and operational requirements of your workload.
Or skip the browser setup
If you need a clean PDF or screenshot from a URL without maintaining a browser-rendering setup, ScreenshotNeo provides a single request API. It can capture a PDF with page size, margins, landscape mode, and page ranges, while also supporting custom CSS and JavaScript when the page needs adjustment. See the ScreenshotNeo documentation for the available parameters.
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}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server so Claude, Cursor, and other MCP clients can take screenshots, inspect pages, and capture PDFs. 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 deleting every page break always solve the issue?
No. You still need breaks between intentional page wrappers. Remove the break only after the final wrapper.
Should I use break-after or page-break-after?
Use both when supporting mixed engines, but scope both declarations to non-final wrappers. wkhtmltopdf relies on older WebKit behavior, so test the exact renderer.
Is 99.9% a permanent fix?
It is a useful diagnostic and documented workaround for fractional overflow, especially in A4 landscape. Correct the box sizing and page dimensions when possible.
Why does the PDF look fine in Chrome?
Chrome and wkhtmltopdf use different rendering engines and pagination algorithms. Browser preview success does not prove that wkhtmltopdf will paginate the same way.
What should I include in a bug report?
Include the minimal HTML/CSS, wkhtmltopdf version and build, operating system, page size, orientation, DPI, all margins, header/footer options, and the exact command.


