From b3062a394821546dbea720f9a49b0fb3fe1be117 Mon Sep 17 00:00:00 2001 From: Flotapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Fri, 9 Oct 2026 03:28:23 +0200 Subject: [PATCH] official URLs on product pages: 157 added, 30 repaired, 4 phantom pages removed (#2912) * fold the cold-start slugs into their vendor, and let a URL ship without prose Two things stood between 192 product pages and an official outbound link. The cold-start cohort added yesterday gave each of its four `-cold` slugs its own product page: /products/alchemy-cold and three siblings, indexable, in the sitemap, duplicates of the real vendor with no description and no link, because the registry has no entry for a measurement arm. They are the same four vendors on a connection that is never reused, so they belong on the vendor's page. PRODUCT_ALIASES folds them, which collapses their bench appearances onto /products/alchemy and removes the four pages from generateStaticParams and the sitemap. It also restores parity with the logo-manifest ALIASES entries these slugs already carry, which the comment on PRODUCT_ALIASES says it mirrors. The other 188 were blocked by the registry type: `description` was required, so nobody could publish a provider's verified homepage without also writing a sober one-liner for it, and the result was pages with no outbound link at all. A verified URL is worth publishing on its own and the description can follow, so `description` is now optional. Every consumer already guarded it except two, both fixed here: the product page's paragraph, and the parent page's "related products" list, which took the child's display name from the first sentence of its description and now falls back to the slug. The link list also stops floating right when there is no paragraph to sit beside it, since a lone link pushed to the far edge with nothing on its left reads as a stray element. No link changes shape: withUtm already tags every rendered outbound href with utm_source=openchainbench, and the registry keeps clean URLs so the JSON, the citations and the structured data stay canonical. tsc --noEmit clean. 22 tests pass across the registry, providers and utm suites. * official URLs for 157 product pages, and repair 30 rotten ones /products/chainstack has an official link. Most product pages did not: the link block needs a registry entry, and a fetch of all 563 published pages found 192 rendering no outbound link at all. Seven of those were my own measurement error (a registry key written in quotes was invisible to the script that read the file, so aster, standx, unigox, conduit, mvx-api, mvx-gateway and xlayer-official were counted as missing while serving a link all along). The rest were real. ADDED: 157 url-only entries. Each URL was researched, then fetched and held to five conditions before it went in: it answers, its final host is the one we asked for and not somewhere it bounced to, the page carries no domain-broker boilerplate, the page names the product, and where several slugs share a host that sharing is deliberate. Several rows of one brand do share a host on purpose, which is what market-category rows (kalshi-crypto, polymarket-sports) and numbered validators (hyper-foundation-1..5) are. UPDATED: 20 existing URLs that no longer point where they should. These had rotted silently, because nothing in the build ever fetches them: ParaSwap is Velora, Avantis is Veranta, Algonode is Nodely, Swirlds Labs is Hashgraph, and avax.network, debridge.finance, everstake.one, gelato.network, moralis.io, nspcc.ru, ramp.network, smartpy.io, kamino.finance and ostium.app have all moved. OpenOcean's apex no longer serves; its app subdomain does. Nozomi's page moved from /products/ to /services/. And oktc-official pointed at www.okx.com/oktc, which 404s today along with /okc and /en/oktc, so OKT Chain falls back to the parent brand. polymarket.com is worth a note because it nearly got "fixed": it failed with an expired certificate here, and the certificate turns out to be CN=*.anj.fr, the French gambling regulator's block page served by DNS interception. The site is fine; the URL stays. DROPPED: 10 URLs whose domain is gone. Seven have no A record at all (ckbapp.dev, echosync.io, iconblockchain.xyz, trysuper.co, trojan.bot, tzbeta.net, wavesnodes.com); three resolve and serve nothing (flowbot.pro, superstack.xyz, kinto.xyz). Each was re-checked against 8.8.8.8 before being called dead, because this machine's resolver blackholes some domains and that looks exactly like an expired one: apex.exchange read as a refused connection locally and is in fact alive, so its link stays. NOT PUBLISHED, on purpose: taiyorobotics.com for graphite-protocol, which never names Graphite; hypurrscan.io for hypurrscanning, where the operator is inferred from the validator's name alone; vibeliquid.xyz, which answers 503. Joint-venture validators written a-x-b-x-c mostly have no site of their own and keep none. neutron.org is left as it is and flagged instead: every host under it, apex and www and app and docs, redirects to one tweet, and guessing a replacement would be worse than reporting it. Links need no change to carry attribution: withUtm already tags every rendered outbound href with utm_source=openchainbench, and leaves the stored URL clean so the JSON, the citations and the structured data stay canonical. The product page now renders the link only when there is one and the section only when it holds something, so a dropped URL leaves no empty row. tsc --noEmit clean. pnpm validate clean. 492 tests pass. --- src/app/products/[slug]/page.tsx | 56 +++++-- src/data/provider-registry.ts | 246 +++++++++++++++++++++++++++---- src/lib/providers.ts | 12 ++ 3 files changed, 266 insertions(+), 48 deletions(-) diff --git a/src/app/products/[slug]/page.tsx b/src/app/products/[slug]/page.tsx index 09d0e2bc5..155bd2e9e 100644 --- a/src/app/products/[slug]/page.tsx +++ b/src/app/products/[slug]/page.tsx @@ -837,7 +837,13 @@ export default async function ProviderPage({ const parentReg = parentSlug ? getProviderRegistry(parentSlug) : undefined; const children = Object.entries(PROVIDER_REGISTRY) .filter(([childSlug, e]) => e.parent === p.slug && childSlug !== p.slug) - .map(([childSlug, e]) => ({ slug: childSlug, name: e.description.split(".")[0] || childSlug })); + // The child's display name is the first sentence of its description. + // An entry that carries only a verified URL has none, so fall back + // to the slug rather than crashing the parent's page. + .map(([childSlug, e]) => ({ + slug: childSlug, + name: e.description?.split(".")[0] || childSlug, + })); return ( <>
@@ -1034,22 +1040,40 @@ export default async function ProviderPage({ return ; })()} - {reg && ( + {/* Now that url and description are both optional, an entry can exist + and have nothing to show (a dead domain dropped from an entry that + never had prose). Render the section only when it holds something. */} + {reg && (reg.description || reg.url || reg.twitter) && (
-

- {reg.description} -

-