OG Images
How social share cards are generated with bin/rails og:images, and how to add your own.
The social share card that shows up when someone pastes a link into Slack, X, or iMessage is a
generated PNG, not a hand-drawn image. bin/rails og:images builds one per coding-agent landing
page, bin/rails og:docs builds one per docs category and page (including the one this guide is
talking about), and bin/rails og:pages builds the site-wide fallback plus one per marketing page
and blog post.
How it works
Every task renders the SAME template, lib/tasks/templates/og_card.html.erb, with different
props: kicker, title, subtitle, tags, accent and title size. It writes a local HTML file and
screenshots it with headless Chrome at 2x device-scale-factor, landing on a 2400x1260 PNG. One
template rather than one per card type, so a new page cannot invent its own card design.
The card is a standalone file opened over file://, so it can link nothing: its :root block is
rendered from config.x.brand_colors (the Ruby mirror of 01-tokens.css), and the two webfonts
are embedded as base64 data: URLs. A browser renders the card rather than an SVG
rasterizer, because the card is the design system at a larger size, and the only
renderer that lays out the type the way the live site does is the engine the live site runs in.
This needs Chrome locally. On the very first run Selenium Manager fetches a matching driver, which is the only step that touches the network -- the fonts are embedded, not fetched. It is build-time only: the generated PNGs are committed to the repository, and nothing at runtime or in CI depends on Selenium being available.
Generating cards
bin/rails og:images # every coding-agent page
bin/rails 'og:images[claude]' # just one, while tuning the layout
bin/rails og:docs # every docs category and page
bin/rails 'og:docs[mobile-apps]' # just one category
bin/rails og:pages # marketing pages, blog posts, and public/og.png
Docs cards land in public/og/docs/, named <category>--<slug>.png for an article and
<category>.png for a category's own index page. All three tasks share one renderer and one template, so
adding a new docs page and running og:docs is the entire workflow; there's no design tool
involved.
Why a missing card fails loudly
If a page's card hasn't been generated, the site doesn't show a broken image; ApplicationHelper
falls back to the site-wide default card (/og.png) automatically, so nothing looks obviously
wrong in a browser. That silent fallback is exactly why a spec exists that checks every real page
and category has its own generated file, and fails the build if one is missing. See
spec/requests/docs_og_cards_spec.rb. Run the relevant og:images task, commit the new PNGs, and
the spec passes again.
Adding your own custom card
If a specific page wants a hand-supplied image instead of a generated one, blog posts support this
today through the image front-matter field, which overrides the generated fallback entirely. Docs
pages follow the same image field for the rare page that needs it, though most docs pages are
better served by the generated card, since it stays visually consistent with every other page in
the category automatically.
Next
You've now covered the whole content and SEO layer. See how the mobile apps reuse these same web views: Mobile Overview.