feat(stats): add comparison seo pages

This commit is contained in:
Adam
2026-07-10 12:09:56 -05:00
parent 4a1982f5c9
commit 233f069070
8 changed files with 1638 additions and 1160 deletions
@@ -32,6 +32,11 @@ export function comparisonHref(first: ComparisonModelRef, second: ComparisonMode
)}/${catalogSlug(second.slug)}`
}
export function canonicalComparisonHref(first: ComparisonModelRef, second: ComparisonModelRef) {
const models = [first, second].toSorted((a, b) => modelKey(a).localeCompare(modelKey(b)))
return comparisonHref(models[0], models[1])
}
export function uniqueComparisonPairs(pairs: ComparisonPair[]) {
return pairs.reduce<{ keys: Set<string>; pairs: ComparisonPair[] }>(
(result, pair) => {
@@ -80,7 +85,7 @@ function FeaturedComparisonCard(props: { pair: ComparisonPair }) {
return (
<a
data-component="compare-home-card"
href={comparisonHref(props.pair.first, props.pair.second)}
href={canonicalComparisonHref(props.pair.first, props.pair.second)}
aria-label={`${props.pair.detail}: ${props.pair.first.name} vs ${props.pair.second.name}`}
>
<span data-slot="compare-home-card-head">
@@ -106,7 +111,7 @@ function FeaturedComparisonCard(props: { pair: ComparisonPair }) {
function ComparisonPanelCard(props: { pair: ComparisonPair }) {
return (
<a data-component="comparison-card" href={comparisonHref(props.pair.first, props.pair.second)}>
<a data-component="comparison-card" href={canonicalComparisonHref(props.pair.first, props.pair.second)}>
<span>{props.pair.detail}</span>
<strong>
{props.pair.first.name} <em>vs</em> {props.pair.second.name}
@@ -0,0 +1,47 @@
import { Meta, Title } from "@solidjs/meta"
import { createAsync, useParams } from "@solidjs/router"
import { createMemo, Show } from "solid-js"
import ModelCompareDetailPage from "../../../component/model-compare-detail"
import { resolveComparisonFamily } from "../../../lib/comparison-pages"
import { getModelCatalog } from "../../model-catalog"
export default function ModelCompareFamily() {
const params = useParams()
const catalog = createAsync(() => getModelCatalog())
const comparison = createMemo(() => {
const source = catalog()
if (!source) return undefined
const first = resolveComparisonFamily(source, params.firstFamily ?? "")
const second = resolveComparisonFamily(source, params.secondFamily ?? "")
if (!first || !second || first.slug === second.slug) return null
return { first, second }
})
return (
<Show
when={comparison()}
fallback={
<Show when={comparison() === null}>
<Title>Model comparison not found</Title>
<Meta name="robots" content="noindex,follow" />
<main data-page="stats">
<div data-component="empty-state">
<strong>Comparison not found</strong>
<p>Choose two model families to compare.</p>
<a href={`${import.meta.env.BASE_URL}compare`}>Compare models</a>
</div>
</main>
</Show>
}
>
{(resolved) => (
<ModelCompareDetailPage
first={{ lab: resolved().first.model.lab, slug: resolved().first.model.slug }}
second={{ lab: resolved().second.model.lab, slug: resolved().second.model.slug }}
family={resolved()}
catalog={catalog()}
/>
)}
</Show>
)
}
@@ -56,14 +56,18 @@ export type ModelCatalog = {
labs: ModelCatalogLab[]
}
export const getModelCatalog = query(async () => {
"use server"
export async function loadModelCatalog() {
const [models, pricing, labs] = await Promise.all([
fetchCatalogPayload(modelCatalogSourceUrl),
fetchCatalogPayload(modelCatalogPricingUrl),
fetchLabCatalogPayload(modelCatalogLabSourceUrl),
])
return buildModelCatalog(models, pricing, labs)
}
export const getModelCatalog = query(async () => {
"use server"
return loadModelCatalog()
}, "getModelCatalog")
export function findModelCatalogEntry(catalog: ModelCatalog, model: string, lab?: string) {
@@ -0,0 +1,113 @@
import { getStatsHomeData } from "@opencode-ai/stats-core/domain/home"
import { runtime } from "@opencode-ai/stats-core/runtime"
import {
canonicalFamilyComparisonPath,
canonicalModelComparisonPath,
comparisonFamilies,
comparisonSitemapModels,
latestFamilyComparisonPath,
resolveComparisonFamily,
} from "../lib/comparison-pages"
import { baseUrl } from "../lib/language"
import { loadModelCatalog } from "./model-catalog"
type SitemapEntry = {
path: string
lastmod?: string
}
export async function GET() {
const [catalog, stats] = await Promise.all([
loadModelCatalog(),
runtime.runPromise(getStatsHomeData()).catch(() => undefined),
])
const lastmod = sitemapDate(
stats?.updatedAt,
...catalog.models.map((model) => model.lastUpdated ?? model.releaseDate),
)
const families = comparisonFamilies.flatMap((family) => {
const resolved = resolveComparisonFamily(catalog, family.slug)
return resolved ? [resolved] : []
})
const familyComparisons = families.flatMap((first, index) =>
families.slice(index + 1).map((second) => ({
path: canonicalFamilyComparisonPath(first, second),
lastmod: sitemapDate(
stats?.updatedAt,
first.model.lastUpdated ?? first.model.releaseDate,
second.model.lastUpdated ?? second.model.releaseDate,
),
})),
)
const models = comparisonSitemapModels(catalog, stats?.leaderboard["All Users"]["2M"])
const modelComparisons = models.flatMap((first, index) =>
models.slice(index + 1).flatMap((second) => {
if (latestFamilyComparisonPath(catalog, first, second)) return []
return [
{
path: canonicalModelComparisonPath(first, second),
lastmod: sitemapDate(
stats?.updatedAt,
first.lastUpdated ?? first.releaseDate,
second.lastUpdated ?? second.releaseDate,
),
},
]
}),
)
const entries = uniqueSitemapEntries([{ path: "/data/compare", lastmod }, ...familyComparisons, ...modelComparisons])
return new Response(sitemapXml(entries), {
headers: {
"Cache-Control": "public, max-age=300, s-maxage=3600, stale-while-revalidate=86400",
"Content-Type": "application/xml; charset=utf-8",
},
})
}
function uniqueSitemapEntries(entries: SitemapEntry[]) {
return Object.values(
entries.reduce<Record<string, SitemapEntry>>((result, entry) => {
result[entry.path] = entry
return result
}, {}),
).toSorted((a, b) => a.path.localeCompare(b.path))
}
function sitemapXml(entries: SitemapEntry[]) {
const urls = entries
.map(
(entry) => ` <url>
<loc>${escapeXml(new URL(entry.path, baseUrl).toString())}</loc>${
entry.lastmod
? `
<lastmod>${entry.lastmod}</lastmod>`
: ""
}
</url>`,
)
.join("\n")
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls}
</urlset>`
}
function sitemapDate(...values: (string | undefined | null)[]) {
const dates = values.flatMap((value) => {
if (!value) return []
const date = new Date(value)
return Number.isNaN(date.getTime()) ? [] : [date]
})
if (dates.length === 0) return undefined
return new Date(Math.min(Date.now(), Math.max(...dates.map((date) => date.getTime())))).toISOString().slice(0, 10)
}
function escapeXml(value: string) {
return value
.replaceAll("&", "&amp;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
}