OG images
Branded social previews via Next's opengraph-image.tsx, generated on demand from the design tokens.
The web app generates branded OG images on demand using Next.js' built-in opengraph-image.tsx convention plus a small render helper.
Where to look
apps/web/lib/og.tsx— the sharedrenderOg({ title, eyebrow?, accent? })helper. Returns anext/ogImageResponse. Knobs for the title, an uppercase eyebrow, and a one-off accent color override.- Per-route generators:
app/[locale]/opengraph-image.tsx— landingapp/[locale]/posts/opengraph-image.tsx— blog indexapp/[locale]/posts/[slug]/opengraph-image.tsx— per-post (fetches the title from the API)app/[locale]/pricing/opengraph-image.tsx— pricing
Next picks these up automatically and emits the right <meta property="og:image"> tags in the head — no need to wire them into metadata by hand.
Brand tokens
renderOg() reads brand tokens from constants in apps/web/lib/og.tsx because ImageResponse runs server-side and can't read CSS variables. If you change --primary in packages/ui/src/styles/globals.css, update the matching hex in og.tsx (Postgres oklch() doesn't render in OG images either way — keep both in sync).
Brand name and host pick up NEXT_PUBLIC_APP_NAME and NEXT_PUBLIC_APP_URL automatically.
Fonts
Geist Bold is fetched from Google Fonts at runtime. If the network is unreachable (CI sandbox, etc) the helper falls back to the system stack so the build still goes green — just visually less branded. For prod you want the fetch to succeed; cache it via your CDN or vendor the TTF if you'd rather keep OG generation network-free.
Adding a new route
Create app/[locale]/<segment>/opengraph-image.tsx:
import { OG_CONTENT_TYPE, OG_SIZE, renderOg } from "@/lib/og";
export const runtime = "nodejs";
export const alt = "Changelog";
export const size = OG_SIZE;
export const contentType = OG_CONTENT_TYPE;
export default async function OG() {
return renderOg({
eyebrow: "Changelog",
title: "What shipped this week",
});
}Use runtime = "nodejs" (not "edge") — the next/font/google fetch needs full Node.
Checking output locally
http://localhost:3000/<locale>/opengraph-image
http://localhost:3000/<locale>/posts/<slug>/opengraph-imageRight-click → save and drop into opengraph.xyz or your editor's preview. The image is regenerated whenever you reload in dev.