Skip to content

feat(seo): /benchmarks/category/[cat] hub pages (D.2)#743

Merged
Flotapponnier merged 1 commit into
mainfrom
feat/category-pages-and-card-cleanup
Jun 26, 2026
Merged

feat(seo): /benchmarks/category/[cat] hub pages (D.2)#743
Flotapponnier merged 1 commit into
mainfrom
feat/category-pages-and-card-cleanup

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

Final cherry-pick from dev — adds 7 SEO-indexable category hub pages. Deferred from PR D because BenchmarkCard ↔ MiniChart type cascade. Follow-up A (citation.ts isInsufficient → InsufficientCheckInput) silently fixed those, so this lands clean now.

New URLs (ship after deploy)

  • /benchmarks/category/aggregators
  • /benchmarks/category/bridges
  • /benchmarks/category/blockchains
  • /benchmarks/category/trading
  • /benchmarks/category/wallets
  • /benchmarks/category/rpcs
  • /benchmarks/category/nft-apis

Files

  • category/[cat]/page.tsx (new) — server route with breadcrumb + ItemList JSON-LD. generateStaticParams enumerates the 7 hubs.
  • benchmark-grid.tsx (updated) — adds optional lockedCategory + allCategories props. Backward compatible (unset = old behavior on /benchmarks).

Risk

Zero new TS errors (3 baseline unchanged). Sitemap pickup automatic.

…dCategory (D.2)

Final cherry-pick from dev-archive — adds 7 new SEO-indexable hub
pages (one per benchmark category) that target long-tail queries
like 'best crypto aggregator benchmarks 2026'.

D.2 was deferred from PR D because the BenchmarkCard ↔ MiniChart ↔
BenchmarkCardData type chain had cascading TS errors. Follow-up A
(citation.ts isInsufficient → InsufficientCheckInput structural
subset) silently fixed those, so this lands cleanly now.

Files:
- src/app/benchmarks/category/[cat]/page.tsx (new) — server route
  that renders BenchmarkGrid pre-filtered to one category + breadcrumb
  + ItemList JSON-LD. generateStaticParams builds the 7 hub routes
  from CATEGORIES. Each page is fully indexable (canonical, OG, etc.).
- src/components/benchmark-grid.tsx (updated) — adds two optional
  props (lockedCategory, allCategories). Backward compatible: when
  unset the grid behaves exactly as before (used on /benchmarks).
  When set (category hub routes), the grid pre-filters and shows the
  full category pill list so users can hop between hubs.

URLs that ship after deploy:
- /benchmarks/category/aggregators
- /benchmarks/category/bridges
- /benchmarks/category/blockchains
- /benchmarks/category/trading
- /benchmarks/category/wallets
- /benchmarks/category/rpcs
- /benchmarks/category/nft-apis

Each renders the canonical bench grid filtered to that category with
its own H1, meta description, and structured data. Sitemap pickup
automatic on next regen.
@Flotapponnier
Flotapponnier merged commit 8e06f59 into main Jun 26, 2026
1 check failed
Flotapponnier added a commit that referenced this pull request Jun 26, 2026
PR #743 created src/app/benchmarks/category/[cat]/page.tsx + benchmark-grid.tsx referencing @/lib/categories but the helper module was not cherry-picked to main, breaking typecheck.

Also re-apply binance: /logos/binance.svg (was reverted by auto-merge against pre-existing main entry pointing to /logos/bnb.png).
Flotapponnier added a commit that referenced this pull request Jun 26, 2026
PR #743 created src/app/benchmarks/category/[cat]/page.tsx + benchmark-grid.tsx referencing @/lib/categories but the helper module was not cherry-picked to main, breaking typecheck.

Also re-apply binance: /logos/binance.svg (was reverted by auto-merge against pre-existing main entry pointing to /logos/bnb.png).
Flotapponnier added a commit that referenced this pull request Jun 26, 2026
…dCategory (D.2) (#743)

Final cherry-pick from dev-archive — adds 7 new SEO-indexable hub
pages (one per benchmark category) that target long-tail queries
like 'best crypto aggregator benchmarks 2026'.

D.2 was deferred from PR D because the BenchmarkCard ↔ MiniChart ↔
BenchmarkCardData type chain had cascading TS errors. Follow-up A
(citation.ts isInsufficient → InsufficientCheckInput structural
subset) silently fixed those, so this lands cleanly now.

Files:
- src/app/benchmarks/category/[cat]/page.tsx (new) — server route
  that renders BenchmarkGrid pre-filtered to one category + breadcrumb
  + ItemList JSON-LD. generateStaticParams builds the 7 hub routes
  from CATEGORIES. Each page is fully indexable (canonical, OG, etc.).
- src/components/benchmark-grid.tsx (updated) — adds two optional
  props (lockedCategory, allCategories). Backward compatible: when
  unset the grid behaves exactly as before (used on /benchmarks).
  When set (category hub routes), the grid pre-filters and shows the
  full category pill list so users can hop between hubs.

URLs that ship after deploy:
- /benchmarks/category/aggregators
- /benchmarks/category/bridges
- /benchmarks/category/blockchains
- /benchmarks/category/trading
- /benchmarks/category/wallets
- /benchmarks/category/rpcs
- /benchmarks/category/nft-apis

Each renders the canonical bench grid filtered to that category with
its own H1, meta description, and structured data. Sitemap pickup
automatic on next regen.

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Flotapponnier added a commit that referenced this pull request Jun 26, 2026
…dCategory (D.2) (#743)

Final cherry-pick from dev-archive — adds 7 new SEO-indexable hub
pages (one per benchmark category) that target long-tail queries
like 'best crypto aggregator benchmarks 2026'.

D.2 was deferred from PR D because the BenchmarkCard ↔ MiniChart ↔
BenchmarkCardData type chain had cascading TS errors. Follow-up A
(citation.ts isInsufficient → InsufficientCheckInput structural
subset) silently fixed those, so this lands cleanly now.

Files:
- src/app/benchmarks/category/[cat]/page.tsx (new) — server route
  that renders BenchmarkGrid pre-filtered to one category + breadcrumb
  + ItemList JSON-LD. generateStaticParams builds the 7 hub routes
  from CATEGORIES. Each page is fully indexable (canonical, OG, etc.).
- src/components/benchmark-grid.tsx (updated) — adds two optional
  props (lockedCategory, allCategories). Backward compatible: when
  unset the grid behaves exactly as before (used on /benchmarks).
  When set (category hub routes), the grid pre-filters and shows the
  full category pill list so users can hop between hubs.

URLs that ship after deploy:
- /benchmarks/category/aggregators
- /benchmarks/category/bridges
- /benchmarks/category/blockchains
- /benchmarks/category/trading
- /benchmarks/category/wallets
- /benchmarks/category/rpcs
- /benchmarks/category/nft-apis

Each renders the canonical bench grid filtered to that category with
its own H1, meta description, and structured data. Sitemap pickup
automatic on next regen.

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Flotapponnier added a commit that referenced this pull request Jun 26, 2026
* feat(sample-health): wire UI badge + API surface + insufficient silencing (follow-up A) (#741)

Completes PR #732 (sample-health system) by surfacing the computed
dataConfidence/sampleHealth/expectedN to users + machine-readable
consumers. Was dead-code on the data layer until this PR.

UI (ledger-table.tsx):
- Filter rows tagged 'insufficient' out of the sorted ranking.
- 'Low sample' pill on rows with dataConfidence='low' + tooltip
  showing the actual sample-health ratio ('45% of expected').

APIs:
- /api/citable: returns expectedN + dataConfidence per row. When
  bench-wide aggregate is 'insufficient', returns value=null +
  leader=null (refuses to crown a leader from undersized samples).
- /api/stat/[slug]: same treatment + drops 'insufficient' rows
  from the machine-readable ranking array. Per-row sampleHealth +
  dataConfidence exposed for downstream tooling.

Library (citation.ts):
- citationCandidates() drops 'insufficient' rows from the leader
  candidate set.
- fieldValue/leader/headlineSentence return null/'insufficient
  data' when bench-wide is insufficient.
- isInsufficient() helper preserved from main (used by hub cards +
  /api/citable hot path).

Effect: bench pages where YAML declares expected_n now visually
communicate sample confidence; agents/journalists hitting the
citable API can't accidentally quote a leader drawn from 3 data
points; the methodology page's 'n >= 1000' claim becomes honest.

Co-authored-by: Florent Tapponnier <contact@mobula.io>

* feat(seo): /benchmarks/category/[cat] hub pages + BenchmarkGrid lockedCategory (D.2) (#743)

Final cherry-pick from dev-archive — adds 7 new SEO-indexable hub
pages (one per benchmark category) that target long-tail queries
like 'best crypto aggregator benchmarks 2026'.

D.2 was deferred from PR D because the BenchmarkCard ↔ MiniChart ↔
BenchmarkCardData type chain had cascading TS errors. Follow-up A
(citation.ts isInsufficient → InsufficientCheckInput structural
subset) silently fixed those, so this lands cleanly now.

Files:
- src/app/benchmarks/category/[cat]/page.tsx (new) — server route
  that renders BenchmarkGrid pre-filtered to one category + breadcrumb
  + ItemList JSON-LD. generateStaticParams builds the 7 hub routes
  from CATEGORIES. Each page is fully indexable (canonical, OG, etc.).
- src/components/benchmark-grid.tsx (updated) — adds two optional
  props (lockedCategory, allCategories). Backward compatible: when
  unset the grid behaves exactly as before (used on /benchmarks).
  When set (category hub routes), the grid pre-filters and shows the
  full category pill list so users can hop between hubs.

URLs that ship after deploy:
- /benchmarks/category/aggregators
- /benchmarks/category/bridges
- /benchmarks/category/blockchains
- /benchmarks/category/trading
- /benchmarks/category/wallets
- /benchmarks/category/rpcs
- /benchmarks/category/nft-apis

Each renders the canonical bench grid filtered to that category with
its own H1, meta description, and structured data. Sitemap pickup
automatic on next regen.

Co-authored-by: Florent Tapponnier <contact@mobula.io>

* fix(seo): blacklist hex builder slugs and shared composite filter

Drop 15 anonymous 0x... HL builder slugs and 5 dead composite slugs
from /products listing, sitemap and individual /products/<slug> routes.
DEAD_COMPOSITE_SLUGS now lives in providers.ts as the single source of
truth; related-providers.ts imports it. Bumps providers cache key v3->v4.

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
Flotapponnier added a commit that referenced this pull request Jun 26, 2026
* feat(sample-health): wire UI badge + API surface + insufficient silencing (follow-up A) (#741)

Completes PR #732 (sample-health system) by surfacing the computed
dataConfidence/sampleHealth/expectedN to users + machine-readable
consumers. Was dead-code on the data layer until this PR.

UI (ledger-table.tsx):
- Filter rows tagged 'insufficient' out of the sorted ranking.
- 'Low sample' pill on rows with dataConfidence='low' + tooltip
  showing the actual sample-health ratio ('45% of expected').

APIs:
- /api/citable: returns expectedN + dataConfidence per row. When
  bench-wide aggregate is 'insufficient', returns value=null +
  leader=null (refuses to crown a leader from undersized samples).
- /api/stat/[slug]: same treatment + drops 'insufficient' rows
  from the machine-readable ranking array. Per-row sampleHealth +
  dataConfidence exposed for downstream tooling.

Library (citation.ts):
- citationCandidates() drops 'insufficient' rows from the leader
  candidate set.
- fieldValue/leader/headlineSentence return null/'insufficient
  data' when bench-wide is insufficient.
- isInsufficient() helper preserved from main (used by hub cards +
  /api/citable hot path).

Effect: bench pages where YAML declares expected_n now visually
communicate sample confidence; agents/journalists hitting the
citable API can't accidentally quote a leader drawn from 3 data
points; the methodology page's 'n >= 1000' claim becomes honest.

Co-authored-by: Florent Tapponnier <contact@mobula.io>

* feat(seo): /benchmarks/category/[cat] hub pages + BenchmarkGrid lockedCategory (D.2) (#743)

Final cherry-pick from dev-archive — adds 7 new SEO-indexable hub
pages (one per benchmark category) that target long-tail queries
like 'best crypto aggregator benchmarks 2026'.

D.2 was deferred from PR D because the BenchmarkCard ↔ MiniChart ↔
BenchmarkCardData type chain had cascading TS errors. Follow-up A
(citation.ts isInsufficient → InsufficientCheckInput structural
subset) silently fixed those, so this lands cleanly now.

Files:
- src/app/benchmarks/category/[cat]/page.tsx (new) — server route
  that renders BenchmarkGrid pre-filtered to one category + breadcrumb
  + ItemList JSON-LD. generateStaticParams builds the 7 hub routes
  from CATEGORIES. Each page is fully indexable (canonical, OG, etc.).
- src/components/benchmark-grid.tsx (updated) — adds two optional
  props (lockedCategory, allCategories). Backward compatible: when
  unset the grid behaves exactly as before (used on /benchmarks).
  When set (category hub routes), the grid pre-filters and shows the
  full category pill list so users can hop between hubs.

URLs that ship after deploy:
- /benchmarks/category/aggregators
- /benchmarks/category/bridges
- /benchmarks/category/blockchains
- /benchmarks/category/trading
- /benchmarks/category/wallets
- /benchmarks/category/rpcs
- /benchmarks/category/nft-apis

Each renders the canonical bench grid filtered to that category with
its own H1, meta description, and structured data. Sitemap pickup
automatic on next regen.

Co-authored-by: Florent Tapponnier <contact@mobula.io>

* fix(seo): drop dead /alternatives links from perp/PM venue footers + dead compare pair

Two SEO regressions feeding 404s into Google's crawl:

1. /products/<slug> for perp/PM venues without an alternatives YAML
   (lighter, manifold, vertex, grvt, ostium, variational, pacifica,
   aevo, edgex, paradex, extended, myriad, limitless, aster) hardcoded
   a /alternatives/<slug> footer link in PmVenueSection and
   PerpVenueSection. Now gated on loadAlternativeSlugs() membership so
   only venues with an actual YAML emit the link.

2. /compare hub rendered jupiter-vs-raydium even though raydium has
   no bench appearances, so /compare/jupiter-vs-raydium 404s
   downstream. Pre-filter COMPARE_PAIRS against getProvider() so dead
   pairs disappear from the list and the JSON-LD ItemList until both
   sides resolve.

Plus a defensive guard in buildAlternativesReverseMap() so the
emitted alt.slug is always a known live YAML, even if the alt list
source ever changes shape.

---------

Co-authored-by: Florent Tapponnier <contact@mobula.io>
@Flotapponnier
Flotapponnier deleted the feat/category-pages-and-card-cleanup branch July 17, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant