feat(seo): give the home, playstyle and reveals pages their own link preview images (#1135)

Merged
DO
4d ago
+974-104

Closes #1135

What changes

A link to the home page, a playstyle page or, in reveal season, /reveals now previews with an image of its own, and every other page previews with the new home image instead of the old wordmark banner (/og-image.png). Each image is 1200x630:

  • Home: the six featured cards in an arc, each glowing in its ink, under "Find the cards that work together".
  • Playstyle (25): the gallery tile's fan (the hero, then the next four cards with art by name, Locations left out), "Lorcana playstyle · N cards", the name, the tagline and three key cards, in the playstyle's accent.
  • Reveals (in season only): the set's name, "N of 204 cards revealed" with a progress bar, and the five newest revealed cards.

Details:

  • scripts/generate-og-images.mjs draws the page images on a fifth page beside the four card pages, with scripts/og/page-template.html. It writes dist/og/page/{home|reveals|playstyle-<id>}.{sha256 prefix}.jpg and a second record, apps/web/node_modules/.cache/og-pages.json. Locally: 26 page images (27 in a faked season), 2.0 MiB, with no change to the run's 30 s.
  • scripts/lib/pageOgImage.mjs holds the pure parts: what each image shows, its alt text, which image a route names (pageOgKey, ogImageForRoute), the season test, and the parsers for PLAYSTYLE_UI and PER_INK. Its tests are in scripts/lib/__tests__/pageOgImage.test.mjs, and they parse the app's real playstyleUi.ts and revealSet.ts.
  • scripts/prerender.mjs writes each page's own image, or the home image, into every page it captures. isRevealSeasonActive now calls the helper's revealSeasonSet, so the crawl and the generator always agree on the season.
  • Check 13 in scripts/check-rendered-html.mjs fails the deploy when /, a playstyle page or /reveals (when the crawl wrote it) doesn't name its own /og/page/ image, or another prerendered route doesn't name the home image. It also fails when twitter:image differs from og:image, or when the build lacks the file. Check 12's image test is now shared with it.
  • withCardOgImage is renamed withOgImage, since every page uses it now.
  • deploy.yml's dry-run paths add the new helper. build:vercel's fallback message names both checks.
  • Docs: CLAUDE.md's Link previews note and IMAGES.md.

Where it differs from the issue

  • The playstyle image's "Key cards" names the fan's first three distinct names, so a playstyle whose fan holds two Aladdins shows two names, not "Aladdin · Aladdin".
  • homeOgData takes the featured ids and the card list. The issue had it read featuredCards.json's cards directly, but that file's cards carry no image hash.

Verification

  • pnpm test:scripts: 1195 passed (10 new helper tests, 5 for check 13)
  • lint:root, knip, and the CodeScene gate (Code Health 10 on every changed script)
  • Three generator runs and a rebuild give the same 26 file names
  • Every page JPEG is 1200x630, at most 111 KB
  • A sample crawl over a faked reveal season: / and /playstyles/lore-denial name their own /og/page/ image in both tags with alt text, /reveals names its own, and /browse, /playstyles, /ink/amber and /vote name the home image
  • Planted fault: deleting playstyle-lore-denial.*.jpg makes check 13 fail, naming that page
  • A sheet of all 26 images, and /reveals with five reveals, two reveals, and Locations among the newest, looked right to me. It goes to the owner before merge.
  • No app JavaScript or card data changes

After deploy: curl the og:image of / and /playstyles/bounce, check one image's headers, and Scrape Again on / in Facebook's Sharing Debugger.


Generated by Claude Code


Summary by cubic

Gives the home, playstyle, and /reveals pages their own link preview images, so links to them no longer show the site-wide wordmark banner. Every other page the crawl writes now previews with the new home image.

  • The deploy build draws each image (home, all 25 playstyles, and /reveals in reveal season only) from a new template and pure helper; featured and playstyle cards from the next reveal set are drawn and counted too, preview-only cards read as the app's loader merges them.
  • Check 13 fails the deploy when a page names anything but its expected image, when twitter:image differs from og:image, when the file is missing from the build, or when a route the crawl should have written has no page at all.
  • Playstyle accents and reveal-set totals live in app TypeScript, so the helper parses playstyleUi.ts and revealSet.ts as text; their tests fail when either file stops parsing.
  • The crawl, image generator, and check 13 share the reveal-season test, so /reveals is drawn and expected exactly when it is captured.
  • withCardOgImage is renamed withOgImage, since every page now uses it.