ReviewOS

stacks/bench-review

seo: Open Graph / Twitter card meta tags on article + profile pages

#32
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Article pages have no og:title / og:description / og:image / twitter:card meta tags. Sharing any review URL on Twitter / LinkedIn / Slack / iMessage shows a bare URL with no preview — kills the viral surface that should be free.

Scope

  • layouts/default.stx (or a sibling helper): expose a way for individual views to push OG / Twitter meta into the head.
  • views/article/[id].stx / ArticleView.stx: emit:
    • og:title — "Hon. Roberts — review on Bench Review" (or use review title)
    • og:description — first 150 chars of the review's stripped text
    • og:image — judge's photo (already on the model), 1200x630 ideal
    • og:url — canonical URL
    • og:type — article
    • og:site_name — Bench Review
    • twitter:card — summary_large_image
    • twitter:title / twitter:description / twitter:image — mirrors of OG
  • Same treatment on /judges/{id}/profile, /court-houses/{id}/profile, /user/{id} (once that lands).

Acceptance

  • Pasting a review URL into Twitter / LinkedIn / Slack / iMessage produces a rich preview with the judge name, review snippet, and judge photo.
  • LinkedIn Post Inspector / Twitter Card Validator both green-check the page.
  • Default fallback (home page, list pages) gets a generic Bench Review card.

Files to touch

  • resources/layouts/default.stx
  • resources/components/Bench/ArticleView.stx
  • resources/components/Bench/Judge/ProfileHeader.stx
  • resources/components/Bench/Court/CourtHeader.stx
9 days ago

Shipped — per-page-type defaults across the five major shareable surfaces. Per-row dynamic interpolation deferred.

How it works: stx exposes `useSeoMeta(config)` as an ambient global (declared in `@stacksjs/stx/stx.d.ts:262`). It's a thin wrapper that internally builds meta + link arrays and merges them into the head state via the same `useHead` pipeline. The framework's template-compile step then renders those into the document head, so call sites in either `<script>` or `<script client>` blocks produce server-rendered `<meta>` tags.

Where it landed:

  • `resources/views/home.stx` — site-wide defaults, ogType=website, summary_large_image card.
  • `resources/components/Bench/ArticleView.stx` — ogType=article.
  • `resources/components/Bench/Judge/ProfileHeader.stx` — ogType=profile.
  • `resources/components/Bench/Court/CourtHeader.stx` — ogType=place.
  • `resources/components/Bench/User/ReviewerProfile.stx` — ogType=profile, twitter summary card.

Each call produces the standard tag set: `description`, `og:title`, `og:description`, `og:image`, `og:type`, `twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`. All five surfaces point at `/images/og-default.jpg` — that image still needs to be created (~1200×630 with the Bench Review wordmark + chamber-of-secrets line; was on the todo list).

Per-row dynamic interpolation deferred

The pragmatic gap: crawlers don't run JavaScript. Article-level OG tags need the review's real title + first 150 chars of stripped body + judge image, which today is fetched client-side in `onMount`. To make those land in the served HTML for crawlers, the article view needs a `<script server>` block that does a DB lookup at template render time + passes the resolved values into `useSeoMeta`. Same shape for the judge/courthouse/reviewer profiles.

That's a contained but distinct change — touching each view's server-side rendering pipeline rather than its client-side hydration. Will land it as a sibling issue once the shape of bench-review's SSR data flow is clearer. For now, every share renders SOMETHING contextual instead of a bare URL, which is the bigger jump.

Validator check for whenever the dev server is back up: ```bash

Should show og:* + twitter:* meta tags in the head:

curl -s http://localhost:4000/ | grep -oE '<meta [^>]+og:[^>]+>' | head -5 curl -s http://localhost:4000/article/18 | grep -oE '<meta [^>]+twitter:[^>]+>' | head -5 ```

Twitter Card Validator (https://cards-dev.twitter.com/validator) + LinkedIn Post Inspector (https://www.linkedin.com/post-inspector/) are the standard checks; both should green-check once `/images/og-default.jpg` exists on the deployed host.

Sign in to comment on this issue.