Open Graph Image Generator

The OG image generator (scripts/generate_og_images.ts) creates 1200×630 social-media preview cards for every rendered HTML page in dist/. Each card is first composed as SVG — title, route path, logo, optional code panel, optional background image — then rasterized to PNG using the globally installed vips binary. The resulting <meta> tags are injected directly into each page's <head>.

bun og:images

This runs as part of bun ssg and can be re-run standalone after config changes without rebuilding the entire site.

Prerequisites

The generator uses vips (libvips command-line) to rasterize SVG to PNG. It is fetched by the installer and lives at the project root as vips (or vips.exe on Windows). No npm package dependency is required — the SVG composition is pure string templating and the rasterization is a single vips copy subprocess.

CLI Options

bun scripts/generate_og_images.ts [options]
OptionDefaultDescription
--dist <dir>./distRendered site directory
--site-url <url>$SITE_URL (required)Public origin for absolute image URLs
--help-Print usage and exit

If --site-url is not provided and SITE_URL is not in the environment, the script exits with an error — the <meta property="og:image"> tags need an absolute URL.

Output

For each rendered page in dist/, the generator:

  1. Writes a PNG to dist/images/og/{route}.png (e.g. dist/images/og/docs/install.png).
  2. Injects <meta> tags into the page's <head>, wrapped in <!-- reeweb:og:start --> / <!-- reeweb:og:end --> markers so subsequent runs can update tags without disturbing other <head> content.
dist/
├── images/
│   └── og/
│       ├── index.png              ← homepage card
│       ├── docs/
│       │   └── install.png        ← /docs/install card
│       └── blog/
│           └── first-post.png     ← /blog/first-post card

Injected Meta Tags

<!-- reeweb:og:start -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Installation" />
<meta property="og:url" content="https://example.com/docs/install/" />
<meta property="og:image" content="https://example.com/images/og/docs/install.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<!-- reeweb:og:end -->

The markers enable idempotent re-runs — the generator strips the previous block before inserting the new one.

Card Design

Each card is a 1200×630 SVG with these elements:

ElementSourceNotes
Backgroundconfig/og_images colourSolid background_color fill
Background imageFirst <img src="/images/...">Desaturated, overlaid at low opacity. Only when present on the page.
Logoconfig/og_images.logo_pathTinted with logo_color, also rendered large and faded behind text
Labelconfig/og_images.labelSmall uppercase text above the title
Page title<title> tagAuto-wrapped across up to 4 lines, 28–34 characters per line
Route pathPage URLRendered as subtle grey text at the bottom
Code panel (opt)First <pre> code blockDark panel with route comment and code snippet, 3 coloured dots

Background Image

When a page contains an <img> pointing to /images/..., the first such image is base64-encoded, desaturated, and composited behind the text at reduced opacity (62%). This gives documentation pages with screenshots and blog posts with hero images a distinctive social card without manual per-page configuration.

The background image is skipped when the page has no images under /images/.

Code Panel

When show_code is enabled in config and no background image is present, a dark code panel appears on the right side of the card. It extracts the first <pre> block from the page HTML (up to 90 characters) and renders it as a code snippet with three coloured macOS-style dots. If no <pre> block is found, it falls back to "reeweb" as the snippet text.

The code panel is automatically suppressed when a background image is already present, keeping the card clean.

Title Wrapping

The title is read from each page's <title> tag and automatically word-wrapped across up to 4 lines. The character limit per line adapts to the card layout:

LayoutCharacters per line
Background image present28
Code panel visible19
Plain (no image, no code)34

Lines that overflow are truncated with ....

Configuration

Card styling is configured in config/og_images.ts:

export const og_images = {
  logo_path: "images/logo-reepolee-text.svg",
  logo_color: "#ffffff",
  brand_color: "#b40000",
  background_color: "#0f172a",
  label: "Ree Web",
  show_code: true,
} as const;
FieldTypeDescription
logo_pathstringPath to the logo SVG, relative to dist/. Must be an SVG file.
logo_colorstringHex colour for the logo (replaces currentColor in the SVG).
brand_colorstringHex colour for the label text, code-panel dots, and gradient overlay.
background_colorstringHex colour for the card background fill.
labelstringShort uppercase label above the title (e.g. project name or tagline).
show_codebooleanWhen true, renders the code panel on pages without a background image.

This file is project-owned — copy and adjust it in another ReeWeb project to use its own identity without changing the generator itself.

How It Works

  1. Collects rendered pages — walks dist/ with Bun.Glob("**/index.html"), skipping anything under og/ to avoid re-processing generated images.
  2. Reads the logo — loads the SVG from dist/<logo_path>, replaces currentColor with logo_color, and base64-encodes it as a data URL.
  3. For each page:
    • Reads the <title> and first <pre> block from the rendered HTML.
    • Resolves the route path from the HTML file's location.
    • Scans for a background image under /images/.
  4. Composes the SVG — builds the full 1200×630 SVG string with all configured elements.
  5. Rasterizes with vips — writes the SVG to a temp file, runs vips copy <svg> <png>, then removes the temp SVG.
  6. Injects OG tags — strips any existing <!-- reeweb:og:start -->...<!-- reeweb:og:end --> block and inserts the new <meta> tags before </head>.

The rasterization uses vips copy (a single subprocess call per page) rather than a Node/Bun-native SVG renderer, keeping the dependency surface small.

Markers and Idempotency

The <!-- reeweb:og:start --> / <!-- reeweb:og:end --> comment markers ensure the generator can be re-run safely. On each run, the previous block is stripped before a new one is inserted. This means editing config/og_images.ts and running bun og:images again updates every page's tags without leaving stale duplicates.