ShipKit docs
The stack

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 shared renderOg({ title, eyebrow?, accent? }) helper. Returns a next/og ImageResponse. Knobs for the title, an uppercase eyebrow, and a one-off accent color override.
  • Per-route generators:
    • app/[locale]/opengraph-image.tsx — landing
    • app/[locale]/posts/opengraph-image.tsx — blog index
    • app/[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-image

Right-click → save and drop into opengraph.xyz or your editor's preview. The image is regenerated whenever you reload in dev.

On this page