The Grover Problem
The grover gem is elegant: it wraps Puppeteer via a Node.js process and lets you generate PDFs from your Rails views using the full power of Chrome's rendering engine. In development, it just works.
In production, especially on Heroku, Render, or Elastic Beanstalk, you hit the walls:
- Node.js dependency — your Ruby app now needs a Node.js runtime installed system-wide
- Chromium binary — 280MB+ binary that must be installed and maintained
- Memory spikes — each PDF generation spawns a browser process; concurrent requests can easily hit 1-2GB RAM
- Shared dyno limits — Heroku's standard-2x dyno (1GB RAM) can crash under moderate PDF load
- Platform drift — Chromium works differently on macOS dev vs Ubuntu production
Running Grover on Heroku requires the heroku-buildpack-google-chrome buildpack, which adds ~280MB to your slug size. Heroku's 500MB slug limit means you may be forced to upgrade to Eco dynos or use external storage just to fit Chromium in your deployment.
Alternatives at a Glance
| Gem / Tool | Rendering | CSS Support | No binary needed | Best for |
|---|---|---|---|---|
| grover | Chromium | Full CSS | ✗ (Node.js + Chrome) | Dev environments |
| wicked_pdf | wkhtmltopdf | CSS 2.1 only | ✗ (wkhtmltopdf binary) | Simple documents |
| PDFKit | wkhtmltopdf | CSS 2.1 only | ✗ (wkhtmltopdf binary) | Simple documents |
| Prawn | Ruby PDF DSL | No HTML/CSS | ✓ | Programmatic documents |
| SnapAPI | Chromium (managed) | Full CSS3 | ✓ (REST API) | Production, any scale |
Option 1: wicked_pdf / PDFKit
The traditional Rails PDF gems. Both use wkhtmltopdf as the rendering engine — an old but widely-used HTML-to-PDF converter. The good news: no Node.js. The bad news: wkhtmltopdf uses a Chromium 72-era engine and doesn't support modern CSS (flexbox is broken, CSS grid doesn't work, modern web fonts are inconsistent).
# Gemfile
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary' # includes the binary for Linux/macOS
# app/controllers/invoices_controller.rb
def show
@invoice = Invoice.find(params[:id])
respond_to do |format|
format.html
format.pdf do
render pdf: "invoice-#{@invoice.number}",
template: "invoices/show",
layout: "pdf",
page_size: "A4",
margin: { top: 15, bottom: 15, left: 10, right: 10 }
end
end
end
wkhtmltopdf is based on Qt WebKit circa Chromium 72 (released 2018, EOL since). Modern CSS that won't work: CSS Grid, position: sticky, CSS variables (--var), modern web fonts (variable fonts), most CSS animations. If your PDF design uses any of these, you'll need a modern browser renderer.
Option 2: Prawn (Pure Ruby)
Prawn generates PDFs programmatically using a Ruby DSL — no browser, no binary, just a gem. The tradeoff: you build the layout in Ruby code rather than HTML/CSS, which is more work but gives you complete control:
# Gemfile
gem 'prawn'
gem 'prawn-table' # for tables
# lib/pdf/invoice_generator.rb
class InvoiceGenerator
def initialize(invoice)
@invoice = invoice
end
def generate
Prawn::Document.new(page_size: "A4") do |pdf|
pdf.font_families.update("Inter" => { normal: "app/assets/fonts/Inter-Regular.ttf",
bold: "app/assets/fonts/Inter-Bold.ttf" })
pdf.font "Inter"
# Header
pdf.text "Invoice ##{@invoice.number}", size: 28, style: :bold
pdf.move_down 10
pdf.text "Date: #{@invoice.created_at.strftime('%B %d, %Y')}", size: 12
pdf.move_down 30
# Line items table
pdf.table(@invoice.line_items.map { |item| [item.description, item.quantity, item.price] },
headers: ["Description", "Qty", "Price"],
width: pdf.bounds.width)
pdf.move_down 20
pdf.text "Total: #{number_to_currency(@invoice.total)}", size: 16, style: :bold, align: :right
end.render
end
end
# Usage in controller
def show
@invoice = Invoice.find(params[:id])
respond_to do |format|
format.pdf do
pdf = InvoiceGenerator.new(@invoice).generate
send_data pdf, filename: "invoice-#{@invoice.number}.pdf", type: "application/pdf"
end
end
end
Option 3: SnapAPI (No Binary, Full CSS)
SnapAPI gives you Grover-quality rendering (real Chromium) without the Node.js dependency. You render your Rails view to HTML, POST it to the SnapAPI REST endpoint, and get back a PDF binary. Works on any hosting platform:
# Gemfile
gem 'faraday' # or use Net::HTTP directly
# app/services/pdf_generator.rb
class PdfGenerator
SNAPAPI_URL = 'https://api.snapapi.pics/v1/screenshot'.freeze
def initialize(html:, format: 'A4')
@html = html
@format = format
end
def generate
conn = Faraday.new do |f|
f.response :raise_error
end
response = conn.post(SNAPAPI_URL) do |req|
req.headers['X-Api-Key'] = ENV['SNAPAPI_KEY']
req.headers['Content-Type'] = 'application/json'
req.body = {
html: @html,
format: 'pdf',
pdf_format: @format,
print_background: true,
viewport_width: 1024,
wait_until: 'networkidle',
}.to_json
end
response.body
end
end
# app/controllers/invoices_controller.rb
def show
@invoice = Invoice.find(params[:id])
respond_to do |format|
format.html
format.pdf do
html = render_to_string(template: 'invoices/show', layout: 'pdf')
pdf = PdfGenerator.new(html: html).generate
send_data pdf,
filename: "invoice-#{@invoice.number}.pdf",
type: 'application/pdf',
disposition: 'inline'
end
end
end
When sending HTML to SnapAPI, external assets (CSS files, images) must be reachable by our servers. Either inline your CSS using ActionController::Base.helpers.stylesheet_link_tag resolved to actual CSS text, or use CDN URLs for images. Assets referenced via localhost or private IPs will fail to load.
Inlining CSS in Rails views
<%# app/views/layouts/pdf.html.erb %>
<%= yield %>
Pricing Comparison
| Approach | Monthly cost (1K PDFs) | Monthly cost (10K PDFs) | Infrastructure |
|---|---|---|---|
| grover (self-hosted) | Server cost ~$20-50 | +$20-100 more RAM | Node.js + Chromium binary |
| wicked_pdf | Server cost ~$10-20 | +$10-30 | wkhtmltopdf binary |
| Prawn | ~$0 (CPU only) | ~$0 | Pure Ruby, no binary |
| SnapAPI Starter | $19/mo (5K calls) | $79/mo (50K calls) | No binary, REST API |
Which to Choose
Same Chromium rendering as Grover, but via a REST API. No Node.js, no Chromium binary, works on any hosting platform including Heroku free tier. Migration from Grover takes about an hour.
Mature gem, well-documented, and the wkhtmltopdf binary is easier to manage than Chromium. Just avoid modern CSS features.
Pure Ruby, no external binaries, full control over layout. Higher development effort but zero ops burden. Good for structured financial documents.
Get started with SnapAPI's free tier — 200 calls/month, no credit card required.