Skip to content

feat(website): carry link-preview tags on every page - #4813

Open
yihanzhu wants to merge 2 commits into
apache:mainfrom
yihanzhu:website/link-previews
Open

feat(website): carry link-preview tags on every page#4813
yihanzhu wants to merge 2 commits into
apache:mainfrom
yihanzhu:website/link-previews

Conversation

@yihanzhu

Copy link
Copy Markdown
Contributor

Summary

Pasting maka.apache.org into X, Slack or LinkedIn gives a bare link, no card. The live pages carry a <title> and a <meta name="description"> and nothing else those crawlers read: no Open Graph tags, no Twitter Card tags. The root page is worse: it is the hand-written meta refresh from #4660, which the crawlers do not follow, marked noindex, with no description at all. Since #4686 pointed the README at the site, that root URL is the one people paste.

  • Every page now emits og:* and twitter:* tags through one Preview.astro component: the page's own title and description, an absolute og:url, og:locale, and a summary_large_image card. The root page carries the English homepage's set so a pasted bare domain gets the same card.
  • The image is a 1200×630 (2×) render of the homepage hero in the page's language, with the lede, the buttons and everything below the hero dropped. readme-hero.mjs makes it next to the README heroes, from the same built page, so every word on the card is copy the page already carries. Light only: a preview sits on the sharing site's own background, so a dark one would show as a black block in most feeds. The two PNGs are committed under website/src/assets/, like the README heroes under .github/assets/, and go through astro:assets, so the URL changes with the pixels and no crawler serves a stale image from cache.
  • readme-hero.json now also records the copy the social renders were made from, and the existing manifest test compares it, so a headline or scene change fails test:dist until the images are re-rendered, the same contract the README heroes already have.
  • The alt text is a new socialImageAlt field on Copy, so it exists in both languages or the build fails.

The two renders as committed:

enzh-CN
enzh-CN

The README heroes rendered on my machine differ from the committed ones by a few hundred bytes with identical copy, so I reverted those four and committed only the new files plus the manifest.

Verification

  • npm --workspace @maka/website run test:dist: 11 pass, 0 fail. The new test every page carries a complete link preview checks index.html and all four language pages for og:title, og:description, og:url, og:locale, twitter:card, an absolute https://maka.apache.org/_astro/social.<locale>.*.png image URL that matches between og:image and twitter:image, a 1200:630 declared aspect, and that the image file exists in dist/. It fails on main, where none of the tags exist.
  • npm --workspace @maka/website run readme-hero regenerates the two images and the manifest; the manifest test passes against the result.
  • node scripts/asf-license-headers.mjs check and npx biome check website are clean.
  • Emitted head of dist/index.html after build:
<meta property="og:url" content="https://maka.apache.org/en/">
<meta property="og:image" content="https://maka.apache.org/_astro/social.en.d0YBSe5v.png">
<meta name="twitter:card" content="summary_large_image">

Not run: a live X card preview, since that needs the tags to be deployed first. The tag set is the one X's card documentation lists as required for summary_large_image, plus the Open Graph fallbacks it reads when the twitter:* twin is absent.

Review focus

The image choice is the one judgment call. Swapping the PNGs or the socialOnly CSS in readme-hero.mjs does not touch the tag half of the change.

AI use

Select exactly one:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code (Fable 5) wrote the tag component, the render step, the tests and the commit message from my description of the problem and the image direction; I reviewed every line and ran the checks above. The commit carries the Generated-by trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

Pasting maka.apache.org into X, Slack or LinkedIn produced a bare link.
The pages had a title and a description but no Open Graph or Twitter
Card tags, and the root page, which is what people paste, is a meta
refresh those crawlers do not follow, with no description at all.
Every page now carries og:* and twitter:* tags through one Preview
component: the page's own title and description, an absolute og:url,
the locale, and a 1200×630 image of the homepage hero in the page's
language. The root carries the English homepage's set.
readme-hero.mjs renders the two images next to the README heroes, from
the same built page with the lede, the buttons and the sections below
the hero dropped, and records their copy in readme-hero.json so the site
test catches a stale render the way it already does for the README
heroes. Light only: a preview sits on the sharing site's own background.
Generated-by: Claude Code (Fable 5)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the effort/M Under 500 readable lines label Sep 4, 2026
@yihanzhu

Copy link
Copy Markdown
ContributorAuthor

@Astro-Han since the site is yours: noticed that pasting maka.apache.org into an X post doesn't unfurl into a card. Turned out there are no og/twitter tags at all, and the root is a meta refresh the crawlers don't follow. This adds the tags plus a 1200×630 render of the hero as the image, made by the same readme-hero script so nothing new is claimed. If you'd rather a different image just say so, the tag half doesn't depend on it.

@Astro-Han
Astro-Han self-requested a review September 5, 2026 07:01

@Astro-HanAstro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the ping. Reviewed at f638394. The problem is exactly as described: Site.astro on main emits title, description, canonical, hreflang and the icon and nothing a card crawler reads, and the root page from #4660 has no description at all. The fix sits on the one seam the site has: Preview.astro mounted once in the only layout, reused on the hand-written root rather than copied, so there is no second metadata authority. Card titles and descriptions come from the same copy fields the pages already render, og:image resolves to an absolute URL under site, and the two PNGs are 2400×1260, which is the 1200×630 ratio at 2× (I pulled both blobs and looked at them; light, headline and scene fully in frame, no clipped text in either language). The root keeps noindex and the refresh, consistent with why #4660 added them. The test reads the built dist/ and fails on main for lack of any og:title. CI green.

No P0–P2. Four small things, all optional:

  • readme-hero.json now stores the scene text twice: manifest.social[locale] is headline + the same heroText that manifest[locale] already holds (diff lines 7 vs 9, and 10 vs 12). Record only the headline and drop socialText; that is the one thing this PR makes redundant.
  • socialImageAlt is a third hand-typed copy of the positioning line plus the README <img alt>; nothing keeps it in sync when hero copy changes. Derive it from positioning, or leave a comment that it moves with README.md:41.
  • Preview.astro:178 picks the image with locale === 'en' ? … : zhCN, so a third locale silently ships the Chinese card. A Record<Locale, ImageMetadata> makes that a type error, the same guarantee you used for socialImageAlt.
  • The header comment in readme-hero.mjs still says it produces the README hero into .github/assets/; it now also writes website/src/assets/social.*.png.

What code cannot show: whether the cards actually render. After deploy, please run https://maka.apache.org/ and https://maka.apache.org/zh-CN/ through the X card validator, LinkedIn Post Inspector and a Slack paste, and post the result here. The root is the interesting one, since it keeps meta robots noindex and I could not confirm how X treats that. If it turns out to block the card, that is the moment to discuss the root's noindex, not before.

Evidence boundary: static read against main plus the two image blobs; no build, no test run.

AI-assisted review: drafted with Maka; I verified the layout seam, the copy sources, the image dimensions and the manifest duplication myself.

Record only the headline in readme-hero.json, since the scene text was
already there. Compose the card's alt from the positioning line and the
README hero's own alt text, which the site test now holds to README.md.
Key the images and og:locale by Locale so a third language is a type
error rather than the Chinese card. Name both outputs in the render
script's header.
Generated-by: Claude Code (Fable 5)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@yihanzhu

Copy link
Copy Markdown
ContributorAuthor

Thanks, all four taken in 6fd77c5: the manifest keeps just the headline under headline, the images and og:locale are Record<Locale, …>, and the header comment names both outputs. For the alt I went with deriving it: positioning plus a sceneAlt field that the site test now asserts equals the README's own <img alt>, so it can't drift from either. Will run root and /zh-CN/ through the X validator, LinkedIn inspector and a Slack paste once it's deployed and post what comes back, the root's noindex included.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/MUnder 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yihanzhu@Astro-Han