Skip to content

OG images

Markdown

Every listing and category page gets its own 1200×630 Open Graph image, rendered during pnpm build from the listing’s name, tagline, logo and category in your theme’s colours. seo.ogImages: 'generated' is the default; 'static' points every page at public/og-default.png instead.

  1. Leave seo.ogImages on 'generated', or set it.

    site.config.ts
    seo: {
    ogImages: 'generated',
    ogScope: { listings: true, categories: true, pseo: false },
    },

    pnpm check:config answers site.config.ts OK — <name> (<url>); ….

  2. Regenerate the fallback. It is the image the home page, the tag pages and every failed render use, and the shipped file carries the demo’s name.

    Terminal
    pnpm og:default
    public/og-default.png: 1200×630, N bytes — "<site.name>" (<site.tagline>)

    pnpm og:default --check renders to memory and compares; it exits 1 with public/og-default.png is out of date — run: pnpm og:default when the file is stale, which makes it a CI step.

  3. Build and read the og: lines.

    Terminal
    pnpm build
    og: N images to produce (L listings, C categories, 0 pseo, 1 default)
    og: rendered N images in S s (cache hits H)
    [zerodirs:og] engine=native, rendered=R, fallbacks=0, logos … / satori … / resvg …; cache=node_modules/.cache/zerodirs/og/ (…)

    fallbacks=0 is the number to look at.

  4. Open one image: https://<your domain>/og/<listingBase>/<slug>.png — on the demo, /og/tools/chatgpt.png.

    A listing’s OG image as served: the logo tile, the category, the name, the tagline and the site name in the theme colours

Where You should see
The count the build output og: N images to produce (…), N being listings plus categories plus one, then fallbacks=0
The image https://<your domain>/og/<listingBase>/<slug>.png a 1200×630 PNG with that listing’s name and logo
The tag view-source:https://<your domain>/<listingBase>/<slug>/ <meta property="og:image" content="https://<your domain>/og/<listingBase>/<slug>.png">, with og:image:width 1200
The fallback pnpm og:default --check public/og-default.png is up to date (N bytes, "<site.name>")

The images are files, not a runtime. A prerendered endpoint at src/pages/og/ lists every image the scope calls for and renders each one during the build; nothing renders on request.

Page Image Switch
a listing /og/<listingBase>/<slug>.png: logo tile or initial letter, category, name, tagline ogScope.listings, on
a category, page 1 /og/<categoryBase>/<slug>.png: name, description, listing count; later pages reuse it ogScope.categories, on
a generated page /og/<slug>.png: the h1 and the count ogScope.pseo, off
everything else: home, indexes, tags, blog, fixed pages /og/default.png, from site.name and site.description always

Each image is a satori element tree, set in Inter Regular and Bold from src/assets/fonts/og/, turned into SVG and rasterised to PNG by resvg. The native @resvg/resvg-js addon is the default; OG_RENDERER=wasm in .env selects @resvg/resvg-wasm, slower but with no binary to load. It is a build-time variable, read from the environment, never from wrangler.jsonc. Colours come from the preset’s hex ogColors block, because satori cannot read oklch(); a hex theme.tokens.primary replaces the preset’s primary, and any other override is ignored here.

Remote logos are fetched ahead of their render, eight at a time, and SVG logos are rasterised at 256 px before they are embedded. Finished PNGs are cached in node_modules/.cache/zerodirs/og/ under a hash of the template version and every visible field, so a local rebuild only re-renders what changed. Workers Builds does not keep that directory, so a git-push deploy re-renders every image on every build. A logo file edited in place keeps its hash; delete the cache directory to force a render.

Config state The visitor sees /admin/ reports Fix
seo.ogImages: 'static' public/og-default.png on every page; the endpoint emits nothing nothing expected; run pnpm og:default so the file carries your name
a render fails (satori or resvg throws) that page’s og:image serves the fallback bytes; the build goes on with [zerodirs:og] <path>: render failed (…); serving public/og-default.png instead and fallbacks=N nothing read the message, fix the cause, rebuild; a failed render is not cached
the native addon does not load the same images, rendered slower, after [zerodirs:og] @resvg/resvg-js could not be loaded (…); falling back to @resvg/resvg-wasm (set OG_RENDERER=wasm to silence) nothing set OG_RENDERER=wasm in .env
a font file is missing every image falls back: render failed (og: font <path> is missing …) per image, fallbacks=N; pnpm og:default refuses to write nothing restore src/assets/fonts/og/
public/og-default.png is missing under 'static' every og:image is a 404; under 'generated' the first failed render stops the build with og: fallback image <file> is missing (…) nothing pnpm og:default
seo.ogScope.pseo: true one PNG per generated page, each a file against the plan limit nothing turn it off first when the budget warns
the free edition the static image everywhere nothing the paid starter
Field Default What it changes
seo.ogImages 'generated' 'static' uses public/og-default.png everywhere and emits nothing
seo.ogScope.listings true one image per listing
seo.ogScope.categories true one image per category
seo.ogScope.pseo false one image per generated page
theme.preset 'default' the ogColors hex block the image uses
theme.tokens.primary unset a hex value replaces the preset’s primary in the image; an oklch() value does not

See seo and theme.

  • Directorysrc/
    • Directorypages/og/ the prerendered endpoint: one route per image, the og: count line
    • Directoryfeatures/paid/og/
      • template.ts the satori layout and OG_TEMPLATE_VERSION
      • render.ts satori, resvg, the native-to-wasm fallback, the render-failed fallback
      • specs.ts which pages get an image and what goes on it
      • cache.ts the content-hash cache
      • fonts.ts the Inter faces
    • Directoryassets/fonts/og/ Inter Regular and Bold, TTF
  • scripts/og-default.ts pnpm og:default and --check
  • public/og-default.png the fallback and the static image