Skip to content

feat(compare): extend SSR /compare to all models with backward-compatible redirects#382

Merged
functionstackx merged 4 commits into
masterfrom
feat/compare-all-models
May 26, 2026
Merged

feat(compare): extend SSR /compare to all models with backward-compatible redirects#382
functionstackx merged 4 commits into
masterfrom
feat/compare-all-models

Conversation

@functionstackx
Copy link
Copy Markdown
Contributor

@functionstackx functionstackx commented May 26, 2026

Summary

PR #351 introduced /compare/[a]-vs-[b] with SSR'd, indexable, JSON-LD-rich GPU comparison pages — but the SSR fetch, OG image, JSON-LD, and metadata are all hardcoded to DeepSeek R1. Users can change the model client-side via the chart controls, but the indexable/shareable view is DeepSeek-pinned.

This PR extends the route to all 8 models in the dashboard by adding a model prefix to the slug, while keeping every PR #351 URL working via a one-hop 307 redirect.

URL format

Form Example Behavior
New canonical /compare/kimi-k26-h100-vs-h200 200, renders with Kimi K2.6 data
Bare slug (PR #351 form) /compare/h100-vs-h200 307 → /compare/deepseek-r1-h100-vs-h200
Family-level alias /compare/kimi-h100-vs-h200 307 → /compare/kimi-k26-h100-vs-h200
Older-version alias /compare/kimi-k25-h100-vs-h200 307 → /compare/kimi-k26-h100-vs-h200
Same-arch supersession /compare/glm-5-h100-vs-h200 307 → /compare/glm-5-1-h100-vs-h200
Non-canonical GPU order /compare/kimi-k26-h200-vs-h100 307 → /compare/kimi-k26-h100-vs-h200
Any combination /compare/h200-vs-h100?i_seq=1k/1k one-hop 307 → /compare/deepseek-r1-h100-vs-h200?i_seq=1k%2F1k
Unknown model slug /compare/notamodel-h100-vs-h200 404

Query params are preserved across the redirect — the prior PR #351 redirect dropped them, which would have broken bare-slug shared links once every bare slug starts redirecting.

Model slug registry

Canonical slugs (packages/app/src/lib/compare-slug.ts):

Slug DB key Display
deepseek-r1 dsr1 DeepSeek R1
deepseek-v4 dsv4 DeepSeek V4 Pro
kimi-k26 kimik2.6 Kimi K2.6
qwen-3-5 qwen3.5 Qwen 3.5
glm-5-1 glm5.1 GLM 5.1
minimax-m27 minimaxm2.7 MiniMax M2.7
llama-3-3-70b llama70b Llama 3.3 70B
gptoss-120b gptoss120b gpt-oss 120B

URL slugs are deliberately finer-grained than the dashboard's display-grouped model dropdown — kimi-k25 and kimi-k26 are distinct URL slugs even though both DB keys roll up to one "Kimi-K2.5" display option in the UI. Each canonical slug queries exactly one dbKey so the data shown matches the URL.

Cardinality

  • Sitemap: jumps from 36 → 288 compare URLs (8 canonical models × 36 GPU pairs). Verified via curl /sitemap.xml | grep -c '/compare/'.
  • generateStaticParams: emits the same 288 entries for ISR / known-routes.
  • Empty-state: when a (model, pair) has no benchmark data (e.g. Kimi K2.6 on some Hopper pair we haven't run), the page renders the existing empty-state message rather than 404 — matches user-picked behavior from the plan-mode review.

Tests

  • compare-slug.test.ts: 32 unit tests (all passing) covering new canonical form, alias resolution for every alias in COMPARE_MODEL_ALIASES, bare-slug detection, every canonical model round-trip through parser + canonicalizer, rejection cases (unknown model, unknown GPU, same GPU, malformed).
  • compare-redirect.cy.ts: 9 e2e cases covering bare-slug redirect, family-alias redirect, older-version redirect (kimi-k25 → kimi-k26), same-arch redirect (glm-5 → glm-5-1), reversed GPU order through any redirect class, query-param preservation, non-deepseek canonical slugs serving without redirect.
  • compare-table.cy.ts + url-params.cy.ts: visit URLs swapped to canonical model-prefixed form so assertions test the rendered page directly, not the redirected one.

Verification

Local dev server (pnpm dev) curl results:

/compare/h100-vs-h200                                  -> 307 Location: /compare/deepseek-r1-h100-vs-h200
/compare/h200-vs-h100                                  -> 307 Location: /compare/deepseek-r1-h100-vs-h200
/compare/kimi-h100-vs-h200                             -> 307 Location: /compare/kimi-k26-h100-vs-h200
/compare/kimi-k25-h100-vs-h200                         -> 307 Location: /compare/kimi-k26-h100-vs-h200
/compare/glm-5-h100-vs-h200                            -> 307 Location: /compare/glm-5-1-h100-vs-h200
/compare/h100-vs-h200?i_seq=1k/1k&i_prec=fp8           -> 307 Location: /compare/deepseek-r1-h100-vs-h200?i_seq=1k%2F1k&i_prec=fp8
/compare/notamodel-h100-vs-h200                        -> 404
/compare                                               -> 200 (rebuilt index with 8 model sections)
/sitemap.xml                                           -> contains exactly 288 compare URLs

Pre-commit hook ran oxlint, oxfmt, and tsc --noEmit — all clean.

Backward compatibility guarantees

  • Every URL form that worked under PR feat: gpu compare #351 keeps working through the one-hop 307 chain
  • The query string survives the redirect
  • The cache key for getCachedBenchmarks is already keyed on the dbKeys array, so adding new models doesn't bust the existing DeepSeek cache
  • ?g_model= URL param still overrides the slug-derived model on the SSR side for advanced sharers

Test plan

  • Click-through Vercel preview: visit a few canonical URLs (/compare/kimi-k26-mi300x-vs-mi355x, /compare/glm-5-1-h100-vs-gb200) and confirm chart + OG image + JSON-LD reflect the right model
  • Visit /compare/h100-vs-h200 and confirm the URL bar updates to /compare/deepseek-r1-h100-vs-h200
  • Visit a (model, pair) with no benchmark data and confirm the empty-state message renders without crashing
  • Verify /compare index renders 8 model sections with the per-model NVIDIA/AMD/cross-vendor sub-grids
  • curl /sitemap.xml | grep -c '/compare/' returns 288

🤖 Generated with Claude Code


Note

Medium Risk
Touches public URL contracts, permanent redirects, and server-side benchmark/availability queries for SEO and SSR; behavior is heavily tested but wrong slug parsing or redirect rules could break inbound links or canonical URLs.

Overview
Extends /compare from GPU-only slugs to model-prefixed canonical URLs (/compare/{model}-{gpuA}-vs-{gpuB}), with a registry of eight models, alias resolution, and SSR/SEO tied to each model’s dbKeys instead of a hardcoded DeepSeek R1 fetch.

Routing & compat: parseCompareSlug / canonicalCompareSlug now return model + GPUs plus flags for legacy bare URLs and alias models. Non-canonical requests get a one-hop 308 (permanentRedirect) that normalizes model, GPU order, and preserves query strings. Legacy /compare/h100-vs-h200 maps to deepseek-r1-….

Discovery & indexing: New compare-availability.ts filters (model, pair) to combos with benchmark data on both GPUs; used by /compare index (per-model sections), sitemap, generateStaticParams, and OG static params so navigation/crawl lists skip empty pairs (direct hits still get empty-state).

UX/SEO: Detail pages pass modelLabel into the client header/copy; metadata, JSON-LD, and OG eyebrow include the model. Expanded unit and Cypress coverage for redirects and canonical URLs.

Reviewed by Cursor Bugbot for commit ab76784. Bugbot is set up for automated code reviews on this repo. Configure here.

…URLs

PR #351 introduced /compare/[a]-vs-[b] but hardcoded DeepSeek R1 as the
only model the SSR fetch, OG image, JSON-LD, and metadata understand.
This adds a model prefix to the slug so every model the dashboard knows
about gets its own SSR'd, indexable comparison page for every GPU pair.

URL format
==========
- New canonical: /compare/{model-slug}-{a}-vs-{b}
- Bare-slug back-compat: /compare/{a}-vs-{b}  ->  one-hop 307 to
  /compare/deepseek-r1-{a}-vs-{b} so PR #351 inbound links still resolve.
- Query params survive the redirect (?i_seq=1k/1k&i_prec=fp8 etc.) — the
  prior redirect logic dropped them, which would have broken bare-slug
  shared links.

Model slug registry (packages/app/src/lib/compare-slug.ts):
  deepseek-r1, deepseek-v4, kimi-k26, qwen-3-5, glm-5-1, minimax-m27,
  llama-3-3-70b, gptoss-120b  (8 canonical slugs).

Alias slugs that 307 to the canonical version (family-level shortcuts +
older-version supersession):
  deepseek -> deepseek-r1
  kimi, kimi-k25 -> kimi-k26
  glm, glm-5 -> glm-5-1   (same architecture, newer point release wins)
  minimax, minimax-m25 -> minimax-m27
  qwen -> qwen-3-5
  llama -> llama-3-3-70b
  gptoss -> gptoss-120b

URL slugs are deliberately finer-grained than the dashboard's
display-grouped model dropdown — kimi-k25 and kimi-k26 are distinct
slugs even though both DB keys roll up to one "Kimi-K2.5" display
option in the UI. Each slug queries a single dbKey so the data shown
matches the URL exactly.

Parser
======
parseCompareSlug now returns { model, a, b, isLegacyBareSlug,
isAliasModel }. GPU keys contain no hyphens (h100, gb200, mi355x), so
the parser finds the {model}/{a} split by lastIndexOf('-') in the
left half — unambiguous even when the model slug contains hyphens like
deepseek-r1 or glm-5-1. The page-level handler issues one 307 to the
fully canonical URL for any combination of (bare slug + alias model +
non-canonical GPU order).

Per-model data fetch
====================
getCachedBenchmarks(parsed.model.dbKeys) replaces the hardcoded
['dsr1']. The query cache is already keyed on the dbKeys array, so
each model gets its own cache slot with zero extra cache work.

generateStaticParams + sitemap
==============================
allCanonicalCompareSlugs() emits the (canonical models × pairs) cross
product = 8 * 36 = 288 entries. Sitemap jumps from 36 to 288 URLs.
Index page (compare/page.tsx) becomes one section per model, each with
the existing NVIDIA-vs-NVIDIA / AMD-vs-AMD / cross-vendor sub-grid.

OG image + metadata + JSON-LD + page header
===========================================
- OG eyebrow now reads "{model.label} · Head-to-head GPU benchmark"
- Page metadata title is "{model.label} — {GPU label} Inference Benchmark"
- JSON-LD ItemList.name and Dataset.name include the model name, and
  each comparison Observation includes a Model PropertyValue so search
  engines understand "this is a per-model comparison"
- Page-client header eyebrow shows "{model.label} · GPU comparison"
  above the H1 so the URL-grouping is legible without scanning the URL.

Tests
=====
- compare-slug.test.ts: 32 unit tests covering new canonical form,
  alias resolution (every alias key), bare-slug detection, every
  canonical model round-trip through parser + canonicalizer, rejection
  cases (unknown model, unknown GPU, same GPU twice, malformed).
- compare-redirect.cy.ts: 9 e2e cases covering bare-slug redirect,
  alias redirect (kimi -> kimi-k26), older-version redirect
  (kimi-k25 -> kimi-k26, glm-5 -> glm-5-1), reversed GPU order through
  any redirect class, query-param preservation, and non-deepseek
  canonical slugs serving without redirect.
- compare-table.cy.ts + url-params.cy.ts: visit canonical
  model-prefixed URLs directly so assertions are about the rendered
  page, not interleaved with bare-slug redirects.

Backward compatibility
======================
Every URL form that worked under PR #351 — /compare/h100-vs-h200,
/compare/h200-vs-h100 (non-canonical), uppercase — keeps working via
the one-hop 307 redirect chain. ?g_model= URL param still overrides
the slug-derived model on the SSR side for advanced sharers.

Manual verification (dev server):
- 307 Location headers correct for all 5 redirect classes
- Query params preserved: ?i_seq=1k/1k&i_prec=fp8 -> ?i_seq=1k%2F1k&i_prec=fp8
- Invalid model slug -> 404
- Sitemap count: exactly 288 compare URLs
- /compare index: 200 OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview, Comment May 26, 2026 1:31am

Request Review

…k coverage

Only emit /compare/<model>-<a>-vs-<b> URLs where both GPUs actually have
benchmark rows for that model in Neon. Avoids 156 empty pair cards in the
index and ~123 unreachable sitemap entries (288 -> 165).

- compare-availability.ts: cached server-side helper using getAvailabilityData
- compare/page.tsx: async, filtered modelsWithPairs, per-model card sections
- sitemap.ts: async, only canonical (model, pair) combos with data
- [slug]/page.tsx + opengraph-image.tsx: generateStaticParams now async + filtered
- compare-slug.ts: kimi-k26 / glm-5-1 / minimax-m27 dbKeys now include both
  point releases so the canonical slug pulls existing data while staying
  forward-compatible with the newer DB key when it arrives

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…abels

When a canonical compare slug groups two model point releases sharing one
architecture (kimi-k26 -> [k2.5, k2.6], glm-5-1 -> [5, 5.1], minimax-m27 ->
[m2.5, m2.7]), the header, OG image, metadata title, and index card section
now read "Kimi K2.5/K2.6" / "GLM 5/5.1" / "MiniMax M2.5/M2.7" instead of
just the newer-version name. Makes it obvious the page covers both versions'
benchmark data, not only the newer one.

The minimax-m25 -> minimax-m27 alias redirect already worked via the
existing COMPARE_MODEL_ALIASES map; verified live.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bare-slug -> deepseek-r1, alias model -> canonical, older version -> newer
version, and non-canonical GPU order -> alphabetical are all permanent
decisions for this URL space. Using next/navigation's permanentRedirect()
returns 308 instead of 307, letting search engines consolidate link equity
onto the canonical URL instead of keeping the alias indexed separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@functionstackx functionstackx merged commit ad50986 into master May 26, 2026
18 checks passed
@functionstackx functionstackx deleted the feat/compare-all-models branch May 26, 2026 01:33
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