Skip to content

feat: branded dynamic OG/social cards via @vercel/og#1334

Merged
NiallJoeMaher merged 1 commit into
developfrom
feat/dynamic-og-images
Jun 14, 2026
Merged

feat: branded dynamic OG/social cards via @vercel/og#1334
NiallJoeMaher merged 1 commit into
developfrom
feat/dynamic-og-images

Conversation

@NiallJoeMaher

Copy link
Copy Markdown
Contributor

What

Replaces the single Lato/planet OG card with the relaunch design-system social cards. Five Satori-safe templates — main, post, profile, publication, job — served from one /og route and wired into every shareable page from real DB models.

Changes

Core (lib/og/)

  • tokens.ts / fonts.ts / templates.tsx — design tokens, edge Google-font loader (Bricolage / Hanken / JetBrains Mono), and the card components (from the design handoff).
  • app/og/route.tsx — single GET /og handler, dispatches on type. Embeds the white wordmark (public/og/wordmark-white.png) as a cached data URI so Satori never resolves a same-origin <img> mid-render (flaky in dev, a round-trip in prod). Long immutable cache-control; runtime stays edge.
  • lib/og/url.ts — typed builders (one per card) that map our models to the route's exact params, cap tags (2 posts / 3 job·interests), and append v=<updatedAt> so edited records get a fresh card.
  • utils/hue.ts — extracted the deterministic per-string hue (was inline in the source-profile client, now shared) so OG avatars/marks match on-site tints.

Page wiringopenGraph.images + twitter (summary_large_image) on:

  • Static/main: root layout (home + fallback), /about, /advertise, /discussions, /jobs, /tag/[slug], /speakers, /volunteer
  • Dynamic: /[username]/[slug] (article + member-link), /d/[slug] (discussion), /s/[sourceSlug]/[slug] (curated link), /s/[sourceSlug] (publication), /[username] (profile), /jobs/[slug] (job)
  • JSON-LD article image uses the same builder.

Notes / judgement calls

  • Weekly main card exists in the route but no page is wired — the newsletter lives at /letters/[slug] (MDX, no metadata), not a /weekly index. /articles is a 301 redirect, so it's skipped.
  • Curated + member link posts now use the branded link card (with cover thumbnail) instead of the raw coverImage, per the kind=link mapping.
  • Discussion cards omit the author role (the shared ReaderPost type doesn't carry jobTitle; kept the change minimal rather than widening it).
  • Fixed two latent Satori violations in the handoff templates (a wrapper div needing display:flex; width:auto on the wordmark <img>) — required for the cards to render.

Verification

All card types render 1200×630 PNGs; og:image + twitter:image resolve to https://www.codu.co/og?type=… on real pages (checked /about, /jobs). Typecheck + lint clean; structured-data unit tests pass.

🤖 Generated with Claude Code

Replace the single Lato/planet OG card with the relaunch design system:
five Satori-safe templates (main, post, profile, publication, job) served
from one `/og` route, wired into every shareable page from real DB models.

- lib/og/{tokens,fonts,templates}.ts(x): design tokens, Google-font loader
  (edge), and the card components. app/og/route.tsx dispatches on `type` and
  embeds the white wordmark (public/og/wordmark-white.png) as a cached data
  URI so Satori never resolves a same-origin <img> mid-render.
- lib/og/url.ts: typed builders (one per card) that map our models to the
  route's params, cap tags, and append a `v=<updatedAt>` cache-buster.
- utils/hue.ts: extract the deterministic per-string hue (was inline in the
  source-profile client) so OG avatars/marks match on-site tints.
- Add openGraph.images + twitter (summary_large_image) to home/fallback,
  about, advertise, discussions, jobs, tag, speakers, volunteer, and the
  dynamic article/discussion/link/profile/publication/job pages. JSON-LD
  article image uses the same builder.
- Long immutable cache-control on the route; runtime stays edge.

Verified: all card types render 1200x630 PNGs and og:image resolves on
real pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@NiallJoeMaher NiallJoeMaher requested a review from a team as a code owner June 14, 2026 15:10
@vercel

vercel Bot commented Jun 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
codu Building Building Preview, Comment Jun 14, 2026 3:10pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7201c569-95ed-4c9d-9651-84e89b292395

📥 Commits

Reviewing files that changed from the base of the PR and between 9635238 and c3e76d9.

⛔ Files ignored due to path filters (1)
  • public/og/wordmark-white.png is excluded by !**/*.png, !**/*.png
📒 Files selected for processing (22)
  • app/(app)/[username]/[slug]/page.tsx
  • app/(app)/[username]/page.tsx
  • app/(app)/d/[slug]/page.tsx
  • app/(app)/discussions/page.tsx
  • app/(app)/jobs/[slug]/page.tsx
  • app/(app)/jobs/page.tsx
  • app/(app)/s/[sourceSlug]/[slug]/page.tsx
  • app/(app)/s/[sourceSlug]/_sourceProfileClient.tsx
  • app/(app)/s/[sourceSlug]/page.tsx
  • app/(app)/speakers/page.tsx
  • app/(app)/tag/[slug]/page.tsx
  • app/(app)/volunteer/page.tsx
  • app/(marketing)/about/page.tsx
  • app/(marketing)/advertise/page.tsx
  • app/layout.tsx
  • app/og/route.tsx
  • lib/og/fonts.ts
  • lib/og/templates.tsx
  • lib/og/tokens.ts
  • lib/og/url.ts
  • lib/structured-data/schemas/article.ts
  • utils/hue.ts

Walkthrough

Introduces a new lib/og/ module with design tokens, font loading, and Satori-based JSX card templates (MainCard, PostCard, ProfileCard, PublicationCard, JobCard), plus typed URL builders (lib/og/url.ts). The /og edge route is rewritten to dispatch to these templates. All page generateMetadata functions are updated to use the new typed builders instead of inline query strings.

Changes

Dynamic OG Image System

Layer / File(s) Summary
Hue utility and OG design tokens
utils/hue.ts, lib/og/tokens.ts
hueFromString provides deterministic 0–359 hue from any string; lib/og/tokens.ts exports T/FONT color/font constants plus avatarBg, pubBg, initials, and fmtK helpers used by card templates.
Typed OG URL builders
lib/og/url.ts
Adds internal ogUrl serializer (omits null/undefined/empty params), a version cache-busting helper, and five exported typed builders: ogMainImage, ogPostImage, ogProfileImage, ogPublicationImage, ogJobImage.
OG card templates and font loading
lib/og/fonts.ts, lib/og/templates.tsx
lib/og/fonts.ts loads Google Fonts TTFs via spoofed User-Agent for Satori; lib/og/templates.tsx defines MAIN lookup, MainCard, PostCard (cover/no-cover branch), ProfileCard, PublicationCard, JobCard, and the OgImage dispatcher.
OG edge route rewrite
app/og/route.tsx
Rewrites the handler to export runtime/size/contentType, cache fonts and wordmark base64 per-worker, parse type + query params into OgParams, and return ImageResponse(OgImage(params)) with a year-long immutable cache header; Sentry handles errors.
Post/article/discussion metadata consumers
app/(app)/[username]/[slug]/page.tsx, app/(app)/d/[slug]/page.tsx, app/(app)/s/[sourceSlug]/[slug]/page.tsx, lib/structured-data/schemas/article.ts
Adds hostFromUrl helper; selects jobTitle in user queries; switches all article/discussion/link-post generateMetadata OG images to ogPostImage including tags, read time, cover, and source hostname.
Profile/publication/job metadata consumers
app/(app)/[username]/page.tsx, app/(app)/s/[sourceSlug]/page.tsx, app/(app)/s/[sourceSlug]/_sourceProfileClient.tsx, app/(app)/jobs/[slug]/page.tsx
Profile metadata adds follower count query and calls ogProfileImage; publication metadata switches to ogPublicationImage; _sourceProfileClient replaces local hue helper with shared hueFromString; job detail page gains generateMetadata with ogJobImage and JOB_TYPE_LABEL.
Static and listing page metadata
app/layout.tsx, app/(app)/discussions/page.tsx, app/(app)/jobs/page.tsx, app/(app)/speakers/page.tsx, app/(app)/tag/[slug]/page.tsx, app/(app)/volunteer/page.tsx, app/(marketing)/about/page.tsx, app/(marketing)/advertise/page.tsx
All static and listing pages switch openGraph.images and twitter.images from hardcoded paths to ogMainImage(id) constants; app/layout.tsx also adds a twitter metadata block.

Sequence Diagram(s)

sequenceDiagram
  participant PageServer as Page generateMetadata
  participant UrlBuilder as lib/og/url.ts builders
  participant OgRoute as /og Edge Route
  participant FontCache as coduFonts() cache
  participant LogoCache as logo() cache
  participant OgImage as OgImage dispatcher
  participant Satori as ImageResponse/Satori

  PageServer->>UrlBuilder: ogPostImage / ogProfileImage / ogJobImage / ogMainImage
  UrlBuilder-->>PageServer: /og?type=X&... URL string
  Note over PageServer: URL embedded in metadata openGraph/twitter images

  rect rgba(100, 149, 237, 0.5)
    Note over OgRoute,Satori: At social preview fetch time
    OgRoute->>FontCache: await coduFonts()
    OgRoute->>LogoCache: await logo(origin)
    OgRoute->>OgImage: OgImage(OgParams { type, ...fields })
    OgImage-->>OgRoute: React element (MainCard/PostCard/ProfileCard/etc.)
    OgRoute->>Satori: new ImageResponse(element, { fonts, size })
    Satori-->>OgRoute: PNG bytes
    OgRoute-->>PageServer: 200 image/png (immutable 1yr cache)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • codu-code/codu#1144: Modifies the same app/og/route.tsx OG image pipeline and profile metadata in app/(app)/[username]/page.tsx that this PR rewrites.
  • codu-code/codu#1324: Introduces member link-post resolution and generateMetadata in app/(app)/[username]/[slug]/page.tsx, which this PR extends with jobTitle and ogPostImage.
  • codu-code/codu#1322: Modifies app/(app)/[username]/[slug]/page.tsx's generateMetadata for post pages, directly overlapping with this PR's OG image switching for the same route.

Poem

🐇 Hop hop, the images dance,
No more hardcoded strings by chance!
A template for post, job, and face,
Each card rendered with Satori's grace.
The rabbit cached fonts in a flash —
Now every preview looks quite smash! 🎨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dynamic-og-images

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@NiallJoeMaher NiallJoeMaher merged commit 0893dcd into develop Jun 14, 2026
4 of 7 checks passed
@NiallJoeMaher NiallJoeMaher deleted the feat/dynamic-og-images branch June 14, 2026 15:10
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