Migrating from wicked_pdf and Snappy / Already HTML and CSS
Migrating off wicked_pdf or Snappy, the wkhtmltopdf wrappers
Language wrappers that shell out to the wkhtmltopdf binary, where the framework integration is the thing you are actually replacing and the engine change comes along with it.
What ports and what does not
Your views port, and that is more significant here than in a raw wkhtmltopdf migration, because these wrappers rendered framework templates rather than static files. The controller action that returned a PDF, the layout, the partials and the asset helpers all continue to exist: what changes is that instead of handing a rendered string to a local binary, you hand it to an HTTP call. The wrapper's own configuration does not port, and neither does its most convenient feature, which is that it resolved your asset pipeline URLs locally.
What the call becomes
Render the view to a string the way you already do, then POST that string as html to /v1/pdf. The wrapper's options hash maps onto the options object with the same names the raw binary used, because these wrappers were passing flags through. The part with no counterpart is the wrapper's handling of local assets: it could reference a stylesheet on the local filesystem, and a hosted renderer cannot, so every asset URL has to become absolute and publicly reachable, or be inlined into the markup you send.
Side by side
The old shape and the new one, with the parts that have no counterpart called out in comments rather than quietly omitted.
# Before, in a Rails controller
respond_to do |format|
format.pdf do
render pdf: "invoice",
template: "invoices/show",
page_size: "A4",
margin: { top: 20, bottom: 25 },
print_media_type: true
end
end
# After
html = render_to_string(template: "invoices/show", layout: "pdf")
response = Faraday.post("https://api.pdfpipe.xyz/v1/pdf") do |req|
req.headers["Authorization"] = "Bearer #{ENV.fetch('PDFPIPE_KEY')}"
req.headers["Content-Type"] = "application/json"
req.body = {
html: html,
filename: "invoice.pdf",
options: {
format: "A4",
margin: { top: "20mm", bottom: "25mm", left: "15mm", right: "15mm" },
},
}.to_json
end
send_data response.body, filename: "invoice.pdf", type: "application/pdf"
# The asset problem: this stylesheet reference worked because the binary
# ran on the same machine. It will not resolve from a hosted renderer.
# <%= wicked_pdf_stylesheet_link_tag "pdf" %>
# Inline the CSS into the string, or serve it from a public absolute URL.The CSS delta, in both directions
What the old engine accepted that a current one does not, and what it refused that a current one wants. The second direction is the one people forget, and it is where the value of the migration is.
- Everything in the wkhtmltopdf CSS delta applies, because that is the engine you were on. Flexbox and grid start working, float workarounds start behaving differently, and @page becomes live.
- The wrapper's asset helpers were producing file paths or protocol-relative URLs. Both fail from a hosted renderer, and the failure is silent: the stylesheet simply does not load and the document renders with browser defaults.
- Any CSS that depended on the asset pipeline's fingerprinting will need the fingerprinted URL to be public, which for most deployments it already is, but the check is worth doing before rather than after.
What changes without anything erroring
The dangerous list. Each of these produces a different document and no diagnostic, so none of them are caught by a test that only checks the render succeeded.
- The whole document, if the stylesheet does not load. This is the loudest possible visual change and it produces no error at all: a fully rendered, completely unstyled document.
- Everything in the wkhtmltopdf list: page count, fonts, headers and footers.
- Timing. The wrapper ran synchronously in your request, so a slow render was a slow request. It still is, but now the latency includes a network call, and the timeout that governs it is a different one from the one you had configured.
What to diff before cutting over
Before anything else, take the string your view produces and check that every URL in it is absolute and publicly fetchable. That one check prevents the failure that accounts for most of these migrations going wrong. Then diff page counts and extracted text across a representative set, the same as any wkhtmltopdf migration.
The one thing that always breaks
Assets. The wrapper existed partly to solve local asset resolution, and removing it removes that solution. Stylesheets, images and fonts referenced by relative path or by a filesystem helper all stop loading at once, and because a document still comes back, the failure is discovered by looking at it rather than by an exception.
Frequently asked
Do I have to rewrite my templates to leave wicked_pdf and Snappy?
No. The templates are HTML and CSS and they carry across. What needs attention is the delta above: the places the two engines disagree, and the configuration that used to live outside the document and now lives inside it, or the other way round.
Can I run both for a while?
Yes, and it is the safest way to do it. Put both behind one internal function that takes your data and returns bytes, switch on an environment variable, and run the new path on real traffic while the old one still serves. You get a diff on real documents rather than on fixtures, and you keep a way back that does not involve a deploy.
What about the documents already generated?
Nothing here changes them. They are files that already exist. What is worth deciding before the cutover is whether a regenerated document has to match the original byte for byte or merely say the same thing, because for anything with a legal or audit character the answer is usually to keep the original file rather than to be able to reproduce it.
Other migrations
The nearest neighbours first, then others that started from the same kind of tool, because the model matters more than the language.
Migrating off wkhtmltopdf without changing what the pages look like
A command line tool built on a fork of an old browser engine, driven entirely by flags, where almost every flag you were passing has a different home on the other side.
Migrating off dompdf, and getting flexbox back
A pure PHP renderer supporting a subset of CSS 2.1, where the migration's real content is everything your templates could not do and had to work around.
Migrating off self-hosted Puppeteer, an operational migration
The same browser engine on the other side, so nothing about the document changes and everything about running it does.
Migrating off mPDF, and replacing its custom tags
A PHP renderer with its own HTML dialect, where the templates contain tags and attributes that exist only in mPDF and have to be translated rather than carried across.
Migrating off TCPDF, where half the document is drawn and half is HTML
A PHP library that is a drawing API with an HTML renderer bolted on, so most codebases using it are half template and half coordinates, and the two halves migrate differently.
Every migration, grouped by what you are coming from
The full list, sorted by the model the old tool used rather than by its name.
What this API actually does
One page per option and endpoint that exists, which is what the mappings above point at.
Render one of your existing documents through the playground before changing any code. That comparison is the whole of the risk assessment for this migration.