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:

⚠️ Chromium on Heroku requires a buildpack

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 / ToolRenderingCSS SupportNo binary neededBest 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 CSS limitations

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
💡 Inline assets for reliable rendering

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

ApproachMonthly cost (1K PDFs)Monthly cost (10K PDFs)Infrastructure
grover (self-hosted)Server cost ~$20-50+$20-100 more RAMNode.js + Chromium binary
wicked_pdfServer cost ~$10-20+$10-30wkhtmltopdf binary
Prawn~$0 (CPU only)~$0Pure Ruby, no binary
SnapAPI Starter$19/mo (5K calls)$79/mo (50K calls)No binary, REST API

Which to Choose

✅ Use SnapAPI if: you need Grover-quality rendering without Node.js

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.

📋 Use wicked_pdf if: your PDF design is simple and uses only CSS 2.1

Mature gem, well-documented, and the wkhtmltopdf binary is easier to manage than Chromium. Just avoid modern CSS features.

💎 Use Prawn if: you need zero runtime dependencies and can write PDF DSL

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.