feat(site): give /stats complete metadata and a social card - #26
Conversation
/stats hand-rolled a bare Metadata object with only a title and a description, so it was the one public page with no canonical URL, no OpenGraph tags, no Twitter card, and no social image. Sharing it anywhere produced an untitled, imageless link. - Route it through createPageMetadata like every other page, which fills in the canonical, OpenGraph, and Twitter blocks from one description - Add /stats/opengraph-image, which cites the live numbers it is a card for: downloads, versions published, and the latest release - Extract the card copy into lib/stats-social-copy.ts so the numbers and the npm-is-down fallback are unit-testable independent of rendering - Publish /stats in the sitemap, where it was missing The image revalidates hourly alongside the page, so a shared card never cites numbers the page has already moved past. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe stats page now uses shared social metadata, exposes a generated Open Graph image, formats NPM statistics into social-card copy, adds sitemap coverage, and validates the metadata and image output. ChangesStats social metadata
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant OpenGraphImage
participant NPMStatsFetcher
participant createStatsSocialCopy
participant SocialCard
OpenGraphImage->>NPMStatsFetcher: Fetch NPM statistics
NPMStatsFetcher-->>OpenGraphImage: Return statistics or null
OpenGraphImage->>createStatsSocialCopy: Generate card copy
createStatsSocialCopy-->>OpenGraphImage: Return headline, detail, and footer
OpenGraphImage->>SocialCard: Render card copy
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/stats/opengraph-image.tsx (1)
6-12: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: unify the export style for image-route constants.
revalidateis exported directly at its declaration (Line 16), butalt,contentType, andsizeare declared as plainconstand exported together at the bottom (Line 28). Export all four the same way, either all inline or all grouped, to keep the file's convention consistent.♻️ Proposed consistency fix
-const size = { +export const size = { height: 630, width: 1200, }; -const alt = "shadscan usage stats: npm downloads and version adoption"; -const contentType = "image/png"; +export const alt = "shadscan usage stats: npm downloads and version adoption"; +export const contentType = "image/png";-export { alt, contentType, size };Also applies to: 28-28
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/stats/opengraph-image.tsx` around lines 6 - 12, Unify the export style for the image-route constants in opengraph-image by making size, alt, contentType, and revalidate use the same declaration or grouped-export pattern. Update the declarations and bottom export in the route while preserving all existing values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/stats/opengraph-image.tsx`:
- Around line 6-12: Unify the export style for the image-route constants in
opengraph-image by making size, alt, contentType, and revalidate use the same
declaration or grouped-export pattern. Update the declarations and bottom export
in the route while preserving all existing values.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b33c391a-18e3-406b-a8c9-d2ae09e449aa
📒 Files selected for processing (6)
app/sitemap.tsapp/stats/opengraph-image.tsxapp/stats/page.tsxlib/stats-social-copy.tstest/e2e/metadata.spec.tstest/shadscan-web/stats-social-copy.test.ts
/statswas the one public page that hand-rolled its ownMetadataobject instead of going throughcreatePageMetadata:That left it with no canonical URL, no OpenGraph block, no Twitter card, and no social image — so sharing the link anywhere rendered it untitled and imageless. It was also missing from the sitemap.
What's here
createPageMetadata, same as every other page, so canonical / OpenGraph / Twitter all derive from one description. Title becomesShadscan usage stats(theh1staysStats; its e2e spec only asserts the heading).app/stats/opengraph-image.tsx— a card that actually cites the numbers it advertises: downloads, versions published, latest release. It reuses the existingSocialCard, so it sits in the same visual family as the other five.lib/stats-social-copy.ts— the copy-building logic lives here rather than in the route, so the number formatting and the npm-is-down fallback are unit-testable without rendering a PNG./statsadded atdaily, matching how often the numbers move.The image carries its own
revalidate = 3600so it refreshes in step with the page rather than freezing at build time. It's the only OG route with a revalidate, which the build output now reflects:Verification
ultracite checktsc --noEmitvitest run test/shadscan-webplaywright testnext build/stats/opengraph-imageprerendered, 1h revalidateNew e2e asserts the full metadata block for
/statsand that the card is a 1200×630 PNG. Rendered card checked visually.One caveat on coverage: the npm-unavailable fallback is verified by unit test, not end to end.
getNpmStatsruns insideunstable_cache, which sits outside the request context the Playwright test proxy patches, so the OG route reads through to real npm regardless of the mock. The fallback string is deterministic andSocialCardrenders arbitrary strings, so the risk is confined to copy, not rendering.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests