PDF generation in Ruby on Rails
Rails already renders the HTML; `render_to_string` hands it over. This replaces wkhtmltopdf, which has been unmaintained since 2023 and ships a Qt WebKit from roughly 2012.
The code
# app/controllers/invoices_controller.rb
require 'net/http'
def show
respond_to do |format|
format.html
format.pdf do
html = render_to_string(template: 'invoices/show', layout: 'pdf', formats: [:html])
uri = URI('https://api.pdfcraft.dev/v1/render')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV.fetch('PDFCRAFT_API_KEY')}"
request['Content-Type'] = 'application/json'
request.body = {
html: html,
options: { printBackground: true, margin: { top: '20mm', bottom: '20mm' } }
}.to_json
upstream = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 60) do |http|
http.request(request)
end
send_data upstream.body,
filename: "invoice-#{params[:id]}.pdf",
type: 'application/pdf',
disposition: 'attachment'
end
end
endWhat to watch for
- `send_data`, not `send_file` — there is no file on disk, and `send_data` sets the length and disposition correctly for a binary body.
- Use a `pdf` layout without the application chrome. Your nav bar, cookie banner and analytics tags do not belong in an invoice.
- Asset pipeline URLs are relative by default. Set `config.action_controller.asset_host` or the stylesheet will not load and the PDF arrives unstyled.
Why not run the browser yourself
You can. Puppeteer and Playwright both work, and for a script you run by hand they are the right answer. The cost arrives at the point it becomes a service someone depends on: one long-lived browser per process rather than one per request, a semaphore so four renders do not become forty, a hard timeout that force-closes a hung page before it holds a slot forever, a scheduled restart because Chromium leaks, and a Chromium version you are now responsible for patching.
None of that is hard. All of it is work that has nothing to do with your product, and it fails under concurrency rather than in testing — which is the worst time to discover it.
Other frameworks
- PDF generation in Next.js
- PDF generation in Django
- PDF generation in Laravel
- PDF generation in AWS Lambda
- PDF generation in Cloudflare Workers
- PDF generation in Supabase Edge Functions
- PDF generation in Spring Boot
- PDF generation in FastAPI
- PDF generation in Express
- PDF generation in ASP.NET Core
- PDF generation in n8n