Docs · Content & SEO

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.