Skip to content

feat: og image and seo metadata - #11

Merged
eliahilse merged 2 commits into
mainfrom
feat/site-seo
Jul 31, 2026
Merged

feat: og image and seo metadata#11
eliahilse merged 2 commits into
mainfrom
feat/site-seo

Conversation

@eliahilse

Copy link
Copy Markdown
Owner

OG image

A build-time card at /og that is the site: the same domain-warped fbm field as the live WebGL background, sampled once per character cell into the same ASCII ramp, set in Geist Pixel Square, with a radial clearing so the wordmark sits on black.

Prerendered as a static PNG (force-static), so Cloudflare serves it as an asset with no runtime cost.

Two non-obvious things the font needed, both worth recording since they fail silently:

  1. geist's exports map blocks deep paths. require.resolve("geist/dist/fonts/…") and even geist/package.json throw under Node — Bun happily resolves both, so this only breaks in the real build. The woff2 is now read by path with fallbacks.
  2. takumi ignores a raw Buffer. Passing fonts: [buffer] renders byte-identical output to passing no font at all — no error, no warning. It has to be FontDetails ({ name, data }) over a Uint8Array. Verified by byte-diffing renders with and without.

SEO

  • Full metadata: metadataBase, title template, openGraph (type, url, siteName, image), twitter summary_large_image with @eliahilse, canonical, keywords, author, robots with max-image-preview: large
  • sitemap.xml and robots.txt as static routes
  • A pixel favicon (icon.svg) matching the wordmark aesthetic

The previous description ("Runtime observability SDK for coding agents") predated review and council; it now covers all three.

Stacked on nothing — branches off main, safe to merge whenever.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na

eliahilse and others added 2 commits July 31, 2026 14:53
Build-time OG card rendering the same domain-warped field as the live
background, sampled per character cell into the site's ASCII ramp and
set in Geist Pixel Square, with the middle cleared for the wordmark.

Adds full metadata (openGraph, twitter, canonical, robots, keywords),
a sitemap, robots.txt, and a pixel favicon.

Two things the font needed: geist's exports map blocks deep paths
under Node, so the woff2 is read by path; and takumi ignores a raw
Buffer, so it is passed as FontDetails over a Uint8Array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na
@eliahilse
eliahilse merged commit fc0ad31 into main Jul 31, 2026
2 checks passed
eliahilse added a commit that referenced this pull request Jul 31, 2026
#11 shipped an OG card that rendered perfectly at build time and **404'd
in production**. Two causes:

1. `@takumi-rs/image-response`'s Node build uses a **native binary that
cannot run on Cloudflare Workers**, so the route could never execute
there.
2. OpenNext does not ship a prerendered route-handler body as a static
asset, so the build-time PNG was discarded — the route was expected to
run at request time.

`/sitemap.xml` and `/icon.svg` 404'd for the same class of reason
(route-handler and app-convention outputs that never became assets).

## Fix

Everything is now a plain static asset, so nothing has to execute on the
edge:

- `scripts/generate-og.tsx` renders the card in Node at build time and
writes `public/og.png`; wired into `build`, `deploy`, and `preview` so
it can never drift from the design
- `sitemap.xml` and `icon.svg` moved to `public/`
- metadata points at `/og.png`, plus an explicit `icons` entry
- the generator **fails the build** if the pixel font can't be found or
the render comes back suspiciously small — the earlier font bug was
silent, and this makes that class of failure loud
- `public/og.png` is gitignored since it is reproducible from the script

## Verified live on kyora.sh

| path | status |
| --- | --- |
| `/` | 200 text/html |
| `/og.png` | 200 image/png, 126,193 b |
| `/icon.svg` | 200 image/svg+xml |
| `/sitemap.xml` | 200 application/xml |
| `/robots.txt` | 200 text/plain |

`og:image` and `twitter:image` both resolve to the live PNG,
`twitter:card` is `summary_large_image`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01CQy3ZJ5MyUxo4qjZwA93Na

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant