Uh oh!
There was an error while loading. Please reload this page.
feat(website): carry link-preview tags on every page - #4813
Conversation
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>
yihanzhu
commented
Sep 4, 2026
@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
left a comment
There was a problem hiding this comment.
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.jsonnow stores the scene text twice:manifest.social[locale]is headline + the sameheroTextthatmanifest[locale]already holds (diff lines 7 vs 9, and 10 vs 12). Record only the headline and dropsocialText; that is the one thing this PR makes redundant.socialImageAltis 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 frompositioning, or leave a comment that it moves withREADME.md:41.Preview.astro:178picks the image withlocale === 'en' ? … : zhCN, so a third locale silently ships the Chinese card. ARecord<Locale, ImageMetadata>makes that a type error, the same guarantee you used forsocialImageAlt.- The header comment in
readme-hero.mjsstill says it produces the README hero into.github/assets/; it now also writeswebsite/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
commented
Sep 5, 2026
Thanks, all four taken in 6fd77c5: the manifest keeps just the headline under |
Summary
Pasting
maka.apache.orginto 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, markednoindex, with no description at all. Since #4686 pointed the README at the site, that root URL is the one people paste.og:*andtwitter:*tags through onePreview.astrocomponent: the page's own title and description, an absoluteog:url,og:locale, and asummary_large_imagecard. The root page carries the English homepage's set so a pasted bare domain gets the same card.readme-hero.mjsmakes 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 underwebsite/src/assets/, like the README heroes under.github/assets/, and go throughastro:assets, so the URL changes with the pixels and no crawler serves a stale image from cache.readme-hero.jsonnow also records the copy the social renders were made from, and the existing manifest test compares it, so a headline or scene change failstest:distuntil the images are re-rendered, the same contract the README heroes already have.socialImageAltfield onCopy, so it exists in both languages or the build fails.The two renders as committed:
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 testevery page carries a complete link previewchecksindex.htmland all four language pages forog:title,og:description,og:url,og:locale,twitter:card, an absolutehttps://maka.apache.org/_astro/social.<locale>.*.pngimage URL that matches betweenog:imageandtwitter:image, a 1200:630 declared aspect, and that the image file exists indist/. It fails onmain, where none of the tags exist.npm --workspace @maka/website run readme-heroregenerates the two images and the manifest; the manifest test passes against the result.node scripts/asf-license-headers.mjs checkandnpx biome check websiteare clean.dist/index.htmlafter build: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 thetwitter:*twin is absent.Review focus
The image choice is the one judgment call. Swapping the PNGs or the
socialOnlyCSS inreadme-hero.mjsdoes not touch the tag half of the change.AI use
Select exactly one:
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-bytrailer.Checklist
Does this PR entail a change in behavior?