Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use a Ruby HTML-to-PDF wrapper around a real rendering engine. For browser-level HTML and CSS, Grover drives Puppeteer and Chromium. For applications already standardized on wkhtmltopdf, PDFKit (or Rails-oriented Wicked PDF) is the alternative. In every case, pass the complete HTML string, make stylesheet and image URLs resolvable, configure paper and print options, then validate the result in the same environment where it will run.

Choose the renderer before writing code

The Ruby gem is an interface; an external renderer does the actual layout. Your choice affects JavaScript support, modern CSS behavior, installation, and how assets are found.

Option Engine Input and output Asset handling Runtime notes
Grover Puppeteer and Chromium Inline HTML; PDF, PNG, or JPEG bytes Set display_url or rewrite references to absolute URLs The current RubyGems listing (Grover 1.2.10, released April 2, 2026) lists Ruby >= 3.0.0 and < 3.5.0; verify compatibility before deployment
PDFKit wkhtmltopdf HTML string, URL, or file; PDF bytes or a file Use complete paths or configure root_url and protocol The wkhtmltopdf executable must be installed and discoverable
Wicked PDF wkhtmltopdf Rails view integration and PDF responses Stages HTML and assets; confirm resolution in your application Its README lists verified Ruby 2.2–3.2 and Rails 4–7.0, which is documentation rather than a guarantee for newer stacks

Pick Grover when Chromium’s browser rendering is important or the document depends on contemporary CSS and JavaScript. Pick PDFKit when wkhtmltopdf is already an accepted system dependency. In Rails, Wicked PDF can reduce view-integration work, but it still carries wkhtmltopdf’s behavior and operational requirements.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Convert a raw HTML string with Grover

Install the gem and browser dependency

Add Grover to the application bundle and install the Puppeteer dependency described by the Grover project. Chromium must be available to the process that runs the conversion. Keep the gem, Puppeteer, and browser versions compatible with your deployment image; a local laptop setup is not a substitute for a production container or server check.

#1 Best Overall
# Gemfile
gem "grover"

# Then install dependencies according to the Grover/Puppeteer
# instructions for your chosen deployment environment.

Pass HTML and write PDF bytes

Grover.new(html).to_pdf returns PDF bytes. This complete example writes them to a file and supplies a display URL so relative resources can resolve.

require "grover"

html = <<~HTML
  <!doctype html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>Invoice</title>
      <style>
        @page { size: A4; margin: 18mm; }
        body { font-family: Arial, sans-serif; color: #222; }
        h1 { color: #164e63; }
        .total { page-break-inside: avoid; }
      </style>
    </head>
    <body>
      <h1>Invoice 1042</h1>
      <p>Generated from a Ruby HTML string.</p>
      <p class="total"><strong>Total: $125.00</strong></p>
    </body>
  </html>
HTML

pdf = Grover.new(
  html,
  display_url: "https://example.com/"
).to_pdf

File.binwrite("invoice.pdf", pdf)

The documented fallback display URL is http://example.com; do not rely on that default for application assets. Set a display URL that represents the asset base, or change every relative reference to an absolute URL before conversion.

Use options deliberately

Pass PDF options in the constructor for paper size, margins, page ranges, headers, footers, and related Chromium settings supported by your installed Grover version. Keep options close to the document’s requirements rather than copying a large default hash. Confirm the option names against the version in your bundle because wrapper and renderer releases can change.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Print media versus screen media

Puppeteer’s page.pdf() generates a PDF with the print CSS media type by default. That means an existing @media print rule can hide navigation, alter colors, or change layout. If the PDF should look like the screen version, call Puppeteer’s emulateMediaType('screen') before generating the PDF; verify how your Grover version exposes that setting. You can also make the intended print design explicit with @page, print-specific rules, and controlled page breaks.

Make CSS, images, fonts, and links load

Raw HTML commonly contains /styles.css, images/logo.png, web fonts, or JavaScript imports. A browser launched by a server does not automatically share your Rails asset-helper context or your local working directory.

Use a base URL

With Grover, set display_url to the host and path that should resolve relative references. The URL must be reachable from the renderer, including inside a container or private network. If your assets are not publicly reachable, preprocess the HTML to embed them as data URLs or expose an authenticated asset endpoint.

Configure PDFKit paths

PDFKit’s documented approach is to provide complete paths or configure root_url and protocol. Its HTML-string API can return bytes or write directly to a file:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
require "pdfkit"

html = <<~HTML
  <html>
    <head>
      <link rel="stylesheet" href="/assets/invoice.css">
    </head>
    <body><h1>Invoice</h1></body>
  </html>
HTML

kit = PDFKit.new(
  html,
  root_url: "https://example.com",
  protocol: "https"
)

File.binwrite("invoice.pdf", kit.to_pdf)
# Or: kit.to_file("invoice.pdf")

Install wkhtmltopdf manually and ensure the application user can execute it. PDFKit’s project documentation notes that automated installation was removed, so a missing binary is an environment setup problem, not a Ruby syntax problem.

Rails integration with Wicked PDF

Wicked PDF is useful when the source is a Rails view rather than a standalone string. It stages HTML and assets in temporary files before invoking wkhtmltopdf, then can return a PDF response. Follow the gem’s integration steps for your Rails version and verify asset resolution in the deployed environment. Its listed verified versions stop at Rails 7.0 and Ruby 3.2; newer combinations require your own compatibility check.

If you already have a rendered string, PDFKit is usually the more direct API. If you need controller formats, view rendering, and Rails response helpers, Wicked PDF can be the better fit.

Paper, layout, and document correctness

Set page geometry

Define paper size and margins either in renderer options or CSS such as @page. Keep critical content inside the printable area. Headers and footers consume space; account for them when choosing top and bottom margins.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Control page breaks

Use CSS properties such as break-before, break-after, and break-inside: avoid where supported by your engine. Tables, invoices, and cards should be tested with unusually long text, because a single expanded row can move a total or heading onto an unexpected page.

Fonts and images

Wait for web fonts and images to be available before capture. Prefer self-hosted, reachable font files for deterministic output. Check that transparent images, SVGs, and high-resolution raster images render correctly and do not make the PDF unnecessarily large.

A production conversion checklist

  1. Confirm the Ruby version satisfies the selected gem and the deployment image includes its renderer (Chromium/Puppeteer or wkhtmltopdf).
  2. Render the final HTML string, including a doctype, character encoding, styles, and all data needed by the template.
  3. Resolve every relative URL with display_url, root_url/protocol, or absolute paths.
  4. Choose paper size, margins, orientation, page ranges, and header/footer behavior.
  5. Decide whether print or screen media is intended; with Puppeteer, print is the default.
  6. Write bytes atomically, set an appropriate PDF content type, and remove partial files after renderer errors.
  7. Inspect representative PDFs for missing assets, font fallback, clipped content, blank pages, broken page breaks, and very long documents.
  8. Run the same checks in the production container, with its network policy, executable paths, and fonts.

Troubleshooting common failures

“Executable not found” or process spawn errors

Cause: Chromium, Puppeteer’s browser, or wkhtmltopdf is absent or not on the service user’s path. Fix: install the documented dependency in the image, configure its executable path if supported, and test as the same operating-system user as the application.

Images or styles are missing

Cause: relative URLs resolve against the wrong base, or the renderer cannot reach a private host. Fix: set Grover’s display_url, configure PDFKit’s root_url and protocol, or rewrite links to absolute or embedded resources.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The PDF looks different from the web page

Cause: print media is active, unsupported CSS is being used, or JavaScript has not finished. Fix: inspect print rules, use browser-supported CSS, wait for required content, and use screen media only when that is the intended design.

Blank pages, cut-off content, or huge files

Cause: incompatible page-break rules, oversized images, an incorrect viewport, or content that expands after layout. Fix: simplify break rules, constrain media dimensions, optimize images, and test long-content cases rather than only a short sample.

Works locally but fails in production

Cause: different Ruby or gem versions, missing fonts, sandbox restrictions, network access, or an absent system executable. Fix: pin and inspect versions, package required fonts and browsers, log renderer stderr, and reproduce inside the production image.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When a hosted screenshot or PDF endpoint is simpler

Or skip the browser setup

If the input is a URL rather than an in-memory HTML string, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also supports PDF output, full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device and viewport settings, retina scale, paper size, margins, landscape mode, page ranges, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage reporting, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Call the API with one GET request (see the ScreenshotNeo documentation):

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The Free plan includes 1,000 shots each month with no card. Starter is $5 for 3,000 shots, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing provides two months free, and every feature is included on every plan. Sign up for the free ScreenshotNeo plan to try 1,000 screenshots a month without a card.

Ruby, cURL, Python, and Node.js request examples

For a URL-based workflow, the same endpoint can be called from Ruby or another language:

require "net/http"
require "uri"

uri = URI("https://api.screenshotneo.com/v1/shot")
uri.query = URI.encode_www_form(access_key: "YOUR_API_KEY", url: "https://stripe.com")
response = Net::HTTP.get_response(uri)
File.binwrite("shot.webp", response.body)
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Frequently Asked Questions

Can Grover convert an HTML string without saving a temporary file?

Yes. Pass the string to Grover.new and call to_pdf; write the returned bytes wherever your application needs them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Which option should a new Rails application start with?

Use Grover when Chromium rendering and modern browser CSS matter. Use PDFKit or Wicked PDF when wkhtmltopdf is already a deliberate platform dependency and its rendering behavior meets the document requirements.

Why do relative URLs fail only in background jobs?

The job may run without the web process’s host configuration, working directory, or network access. Supply an explicit base URL or absolute resources and test under the job’s deployment identity.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.