Featured
šÆ Print Me Baby One More Time
šµ āOops!⦠I Did It Againā ā Britney Spears
ā Back to Ruby (And Back to PDFs)
Itās been a minute since I last tangoed with Ruby. Honestly? Years. Long enough to forget how bindings in blocks work, fumble my way through rspec
, misuse simplecov
, and set up CI pipelines that make me question my life choices.
Oh, and now we have rbs
and one-liner method definitions? Cute.
But today, weāre diving headfirst into one of the oldest, gnarliest problems in the web dev trenches: generating PDFs from a Rails app.
And yes ā itās still a mess.
š§³ The Old Way: Wkhtmltopdf & Wicked Nostalgia
I needed to render a PDF from HTML. Reflex kicked in.
Grab wicked_pdf
. Pair it with wkhtmltopdf
. Back in the day, that combo was peak developer couture.
At first, things seemed fine. Then came the catch: I needed headers and footers that show up consistently ā not just in pretty HTML preview, but in the actual printed PDF.
So I did what any nostalgic developer would do: went straight to the wkhtmltopdf GitHub repo.
What did I find?
šŖ¦ Archived. Dead. Two years gone. No signs of life.
š¬ Panic Mode & The JavaScript Jungle
Someone on X (RIP Twitter) said, āJust use Prawn.ā
Right. Let me handcraft every layout , manually paginate content, and write 200 lines of line-break handing code by candlelight. No, thanks. Iāve done my time.
Then someone whispered: āGrover.ā
It uses Puppeteer under the hood. Sounds promising ā until you realize it hauls in half of npm and an entire browser just to print an invoice.
Yāall. I just wanted a PDF.
š Going Deeper: Puppeteer & the BiDi Protocol
Grover is just a wrapper around Puppeteer, a JS library that controls Chrome (and Firefox ). Itās slick ā but itās still a Node.js.
Naturally, curiosity got the better of me. If Puppeteer can do it⦠could I ditch the JS layer and go native?
That led me to the BiDi (bidirectional) protocol ā a modern, structured way to talk directly to the browser. I read through the chromium-bidi specs, explored Puppeteerās internals, and spiraled into DevTools docs at 2AM.
Eventually, I got it working.
HTML ā Chrome ā PDF.
I danced. Briefly.
š The PDF Problem: Headers, Footers & Chromeās Quirks
Hereās where it got tricky. I found this magical CSS:
@page {
@top-center {
content: element(pageHeader);
}
}
header.page-header {
position: running(pageHeader);
}
Looked legit. Sounded powerful. Except Chrome took one look and said:
āI donāt know her.ā
Turns out, even in 2025, Chrome still doesnāt support element()
in print styles. This is one of those features that lives in every spec doc and Stack Overflow dream, but in practice? Chrome ghosts it.
In my search for a workaround, I found this fantastic write-up by Idan Co, titled āThe Ultimate Print HTML Template with Header & Footerā.
Itās a deep dive into the realities of getting printed layouts right in the browser.
Spoiler alert: the solution involves using a table and thead and tfoot as empty āplace-holdersā.
Yes. A table. Like itās 2002.
But hereās the thing ā it works. Itās a pragmatic hack that survives Chromeās quirks and avoids the heartbreak of unsupported @page
magic.
So if youāre still in the trenches, wrestling with disappearing footers and floaty headers, that article might just save your weekend.
š Redemption: Paged.js to the Rescue
Just as despair set in, I found Paged.js ā a polyfill for printed web content. And guess what?
⨠It. Actually. Works. āØ
With some setup, you get glorious, print-ready output with real page numbers, headers, and footers that donāt ghost you at the worst moment.
Hereās a sample setup:
@page {
size: A4;
margin: 30mm;
font-family: 'Roboto', sans-serif;
@top-center {
content: element(pageHeader);
}
@bottom-center {
content: element(pageFooter);
}
@bottom-right {
content: counter(page) " / " counter(pages);
}
}
<header class="page-header">
<h1>PDF Rendering Sample</h1>
<p class="text-muted">Styled with Bootstrap & custom CSS</p>
</header>
<footer class="page-footer text-center">
<p class="small text-muted">Generated with ā¤ļø by Bidi2PDF ā Page </p>
</footer>
Pro tip: yes, the header and footer go in the body and they need to be at the top of your page. No, I donāt know why. Just roll with it.
š” Bidi2PDF: The Birth of a Ruby Gem
All this chaos led to something useful:
š Bidi2PDF, a work-in-progress Ruby gem.
It gives you a clean CLI to print any website as a PDF. No Node.js. No giant wrappers. No tears.
Supported out of the box:
- ā Basic Auth
- ā Session cookies
- ā Custom auth headers
- ā Modern print styling via Paged.js
- ā Chrome automation via BiDi
PDF printing remains one of the dark arts of web dev.
But with Bidi2PDF, youāve got a better flashlight.
Footnote: Britney Spearsā āOops!⦠I Did It Againā isnāt just a pop banger ā itās a mood. Especially in dev land. That moment you think, āThis time itāll workā as you reinstall a PDF gem for the fifth time? Britney gets it. The song is basically the emotional soundtrack of jumping back into legacy code, patching print styles, and pretending wkhtmltopdf will behave this time. Itās not just nostalgia ā itās dev dĆ©jĆ vu. And like Britney, we always circle back. Not because itās easy. But because we did it again. š»ā¤ļøšØļø
Created in collaboration with OpenAIās GPT-4.