Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Generate an Open Graph image in Ruby by rendering a fixed-size HTML/CSS card, capturing it with a Chromium-based renderer, storing the resulting PNG at a stable public URL, and placing that URL in the page’s og:image metadata. This approach lets you use normal Rails view data—title, author, logo, category, and publication date—while keeping the design in HTML and CSS.
For most Rails applications, choose Grover when you want a straightforward HTML-to-image wrapper, Ferrum when you need direct Chrome control, or a hosted HTML-to-image API when you do not want to operate a browser binary. The sections below show the self-hosted implementations, deployment details, failure fixes, and a hosted alternative.
The rendering pipeline
- Prepare card data. Select a bounded set of fields such as title, author, brand name, and accent color. Escape user-controlled text through your normal Rails view helpers.
- Render a dedicated template. Build a card at a fixed viewport, commonly 1200 × 630 pixels. Keep this template separate from the article page so layout changes cannot unexpectedly alter social images.
- Capture the HTML. Use Grover or Ferrum to let Chromium perform layout, font loading, image decoding, and CSS rendering.
- Persist the bytes. Write the PNG (or JPEG/WebP) to object storage or another cache and give it a stable, publicly reachable URL.
- Publish metadata. Add that URL to the document head as
og:image. Social crawlers must be able to fetch it without authentication.
A stable URL is preferable to generating a new image during every page request. Regenerate when the title, author, branding, or template version changes, and cache the result by a content digest or record version.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Option 1: Grover and a Rails view
Grover is a Ruby wrapper that sends a URL or inline HTML through Puppeteer and Chromium and can return PNG or JPEG bytes. It is convenient when your card already exists as a Rails template.
#1 Best Overall
Install the Ruby and browser dependencies
Add Grover to your Gemfile, bundle it, and install the Puppeteer/Chromium runtime required by the selected Grover version. Browser installation is part of deployment: your build image, worker, and local development environment all need a compatible Node/Puppeteer and Chromium setup.
# Gemfile
gem "grover"
# shell
bundle install
# Install the Puppeteer package and browser according to your deployment image.
The Grover gem registry lists version 1.2.6 dated January 14, 2026. An opened 1.2.4 page states Ruby >= 3.0.0, < 3.5.0; that requirement is version-specific, so verify the requirements of the exact version you select before upgrading Ruby or locking the gem.
Create a dedicated card template
<!-- app/views/og_cards/show.html.erb -->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
* { box-sizing: border-box; }
html, body { margin: 0; width: 1200px; height: 630px; }
body {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 64px;
color: #f7f7fb;
background: #171827;
font-family: Arial, sans-serif;
}
.eyebrow { color: #9ea7ff; font-size: 24px; font-weight: 700; }
h1 { max-width: 1040px; margin: 24px 0; font-size: sixty; line-height: 1.08; }
.byline { font-size: 26px; color: #c9cbe0; }
.brand { font-size: 24px; font-weight: 700; }
</style>
</head>
<body>
<div>
<div class="eyebrow"><%= h(@post.category) %></div>
<h1><%= h(@post.title) %></h1>
<div class="byline">By <%= h(@post.author.name) %></div>
</div>
<div class="brand">Example Magazine</div>
</body>
</html>
Replace the accidental placeholder value in the example CSS with a valid size such as 60px before use. The important properties are the explicit 1200 × 630 canvas and bounded text. Long titles need a tested line limit, smaller type, or an ellipsis policy so they cannot overflow.
Free tools Windows power users keep installed
One-click scans. No signup required.
Render and save the PNG
# app/services/open_graph_image.rb
class OpenGraphImage
def self.generate(post)
html = ApplicationController.render(
template: "og_cards/show",
assigns: { post: post }
)
image = Grover.new(
html,
display_url: "https://example.com/og-cards/#{post.id}"
).to_png(
viewport: { width: 1200, height: 630 },
full_page: false
)
Rails.root.join("tmp", "og-#{post.cache_key_with_version}.png").tap do |path|
File.binwrite(path, image)
end.to_s
end
end
Grover’s documentation demonstrates rendering a Rails template to a string before passing it to Grover. The display_url is significant when the HTML contains relative URLs: without an appropriate base URL, Chromium may resolve relative images, stylesheets, or fonts against its default display URL. Using absolute asset URLs is another reliable solution.
Rank #2
Make generation asynchronous
Do not make a reader wait for Chromium unless that behavior is intentional. Enqueue a job after publishing or editing a post, write the bytes to your storage layer, and update the record with the public image URL. A cache key containing the post version and template version prevents stale cards while avoiding duplicate renders. The implementation should also clean up temporary files after upload.
Option 2: Ferrum for direct Chrome control
Ferrum is a high-level Ruby API for Chrome. It communicates through the Chrome DevTools Protocol and does not require Selenium, WebDriver, or ChromeDriver. It runs headless by default, but Chrome or Chromium remains a production dependency.
Minimal screenshot service
require "ferrum"
class CardScreenshot
def self.capture(url, output_path)
browser = Ferrum::Browser.new(
browser_path: ENV["BROWSER_PATH"] # omit when Chrome is already on PATH
)
browser.go_to(url)
browser.resize(width: 1200, height: 630)
browser.screenshot(path: output_path, full: false)
ensure
browser&.quit
end
end
CardScreenshot.capture(
"https://example.com/og-cards/42",
"tmp/og-42.png"
)
Ferrum’s documented setup allows Chrome to be found on PATH or through BROWSER_PATH. Always call quit, including on errors, or worker processes can accumulate browser instances. For a private Rails template, navigate to a route that renders only the card and protect that route with an internal authorization mechanism; for a public route, ensure the social crawler can reach it.
When Ferrum is the better fit
- Use it when you need direct navigation, viewport, JavaScript, or browser lifecycle control.
- Use Grover when you prefer a higher-level “HTML in, image bytes out” interface.
- Both approaches require browser-related deployment work and should be tested in the same type of container used by production workers.
Hosted HTML-to-image rendering
A hosted Ruby client can accept HTML and return an image URL, removing the need to package and patch a local browser. The documented html2img client describes Open Graph and per-post images as use cases, demonstrates a 1200 × 630 render, requires Ruby 3.1 or newer and an API key, and states that free-tier renders are hosted for seven days while paid-plan renders are permanent. Retention and pricing are service terms, so confirm them when you integrate.
Rank #3
Keep the API key on the server, not in browser JavaScript. Evaluate the provider’s latency, privacy policy, retention, failure behavior, and continuity requirements before sending private or unpublished content. A hosted service changes the operational burden; the available documentation does not establish that it is faster than Grover or Ferrum.
Design and asset rules that prevent broken cards
Fonts and images
- Wait for web fonts and images before capturing. A screenshot taken before they finish loading can fall back to a different font or show empty image boxes.
- Prefer absolute, publicly reachable asset URLs or provide a correct base/display URL. Relative paths are a documented Grover failure point.
- Use local assets or a controlled asset host when reproducibility matters. External services can be unavailable during a background job.
Text and accessibility
- Escape titles, author names, and category values with the normal Rails view escaping.
- Set explicit line-height, width, and maximum text length. Test the longest real title, not only a short sample.
- Do not put essential information only in a tiny footer; social previews are often displayed at small sizes.
Metadata in the page
<meta property="og:image" content="https://cdn.example.com/og/posts/42-v3.png">
The image URL should remain stable long enough for crawlers to cache it, return an image content type, and work without cookies or a logged-in session. If you replace an image at the same URL, some platforms may continue showing an older cached copy; versioned filenames avoid that ambiguity.
Testing, performance, and cost decisions
Test the complete path
- Open the generated URL directly in a clean browser and confirm the dimensions and content type.
- Inspect the page source to verify the final
og:imagevalue, not merely a client-side DOM update. - Test records with long titles, missing avatars, unusual Unicode characters, and slow images.
- Run the same job in the production-like worker image, including its fonts, Chrome binary, environment variables, and network policy.
Control rendering overhead
Reuse a browser process carefully or create one per isolated job according to your worker model; the reviewed documentation does not provide a universal throughput number. Limit concurrency to the CPU and memory available to your workers, and measure your own queue time and render duration. Cache by record and template version so a page view does not trigger a new capture.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesChoose the operational model
| Approach | Control | What you operate | Image lifecycle |
|---|---|---|---|
| Grover | HTML template through a Puppeteer/Chromium wrapper | Ruby gem, Node/Puppeteer, Chromium, worker resources | Your storage and public URL |
| Ferrum | Direct Chrome DevTools Protocol control | Ruby process, Chrome/Chromium binary, cleanup, workers | Your storage and public URL |
| Hosted html2img client | HTML request and provider options | API key, vendor integration, privacy and continuity review | Documented URL output; retention depends on plan |
Troubleshooting
The image is blank or partly rendered
Check that the worker can reach every asset URL, that the browser is not blocked by authentication, and that capture waits for the required selector, fonts, or images. Log the final HTML and the browser console when diagnosing a failing template.
Rank #4
Images or styles are missing
Replace relative paths with absolute URLs or set Grover’s display_url. Confirm that the asset host permits requests from the worker and that HTTPS certificates are valid inside the container.
Chrome cannot start
Install a compatible Chromium package, expose its binary on PATH, or set Ferrum’s BROWSER_PATH. Verify sandbox and shared-memory settings for your container rather than copying flags blindly.
Ruby or gem version conflicts
Check the exact Grover release’s Ruby requirement; requirements from an older registry page do not automatically apply to a newer release. Lock the gem and browser versions together in deployment.
Cards are stale
Include the post version and template version in the storage key or URL. Regenerate after edits and update the metadata URL when you need crawlers to fetch a new asset.
Best Value
Or skip the browser setup
ScreenshotNeo provides a hosted screenshot API and MCP server. It removes cookie/consent banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—let Claude, Cursor, or another MCP client request captures without custom browser orchestration.
For a card route that your application exposes publicly, make one GET request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/og-cards/42 -o shot.webp
The API also supports PNG, JPEG, WebP, full-page or element captures, custom CSS and JavaScript, waits, device presets, retina scale, headers, cookies, user agents, geolocation, caching TTLs, signed image links, asynchronous webhooks, and bulk capture. See the ScreenshotNeo documentation for parameter names and response details. The parameter names used by other screenshot APIs are accepted, which can simplify a migration.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account when you want to avoid packaging and maintaining a browser.
Frequently Asked Questions
Should an Open Graph image be generated synchronously during a page request?
Usually no. Generate it when the post is created or changed, store it, and serve the saved URL so social crawlers and readers do not wait for a browser render.
Can I use a private Rails route as the screenshot source?
Only if the renderer can authenticate. A public, minimal card route or inline HTML avoids session and permission problems; never expose unpublished content unintentionally.
Which output format should I store?
PNG is a dependable default for text-heavy cards. JPEG or WebP can reduce bytes when their visual quality and crawler support meet your requirements; verify the actual response and platform behavior.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Quick Recap
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.

