feat(playground): add chat app comparison pages#2779
Conversation
Add SEO comparison pages under /compare positioning LLM Gateway Chat against ChatGPT, Claude, Gemini, Poe, T3 Chat, Perplexity, and OpenRouter. - Single source of truth in src/lib/comparisons.ts (one profile per competitor with table rows, paragraph sections, who-it's-for, migration, and FAQ), mirroring chat-plan pricing from @llmgateway/shared - Dynamic /compare/[slug] vs-pages with generateStaticParams, generateMetadata, plus FAQPage and BreadcrumbList JSON-LD; each page also targets '[competitor] alternative' intent - /compare hub grouping competitors by category - Reusable logo face-off and comparison-table components - Wire compare routes into sitemap.ts and link the hub from the home SEO section Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds a complete ChangesCompare Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/playground/src/components/compare/comparison-table.tsx (1)
23-23: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winRemove section-label JSX comments to match repo TSX guidelines.
These comments are purely structural and can be dropped to keep the file aligned with the project’s “no unnecessary code comments” rule.
As per coding guidelines:
**/*.{ts,tsx}→ “No unnecessary code comments”.Also applies to: 41-41
🤖 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 `@apps/playground/src/components/compare/comparison-table.tsx` at line 23, Remove the unnecessary JSX section-label comments from the comparison-table.tsx file. Specifically, delete the comment at line 23 that reads `{/* Column headers */}` and the similar comment at line 41, as these purely structural comments do not align with the project's coding guidelines that prohibit unnecessary code comments in TSX files.Source: Coding guidelines
apps/playground/src/app/compare/[slug]/page.tsx (1)
119-119: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winRemove section-heading JSX comments to comply with TSX style rules.
These comments are organizational labels only; removing them keeps the page aligned with the repository’s comment policy.
As per coding guidelines:
**/*.{ts,tsx}→ “No unnecessary code comments”.Also applies to: 140-140, 150-150, 175-175, 188-188, 229-229, 270-270, 294-294, 350-350, 370-370, 392-392
🤖 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 `@apps/playground/src/app/compare/`[slug]/page.tsx at line 119, Remove all unnecessary JSX organizational comments from the page component in the compare page file. Specifically, delete the section-heading comments like {/* Hero */}, {/* ... */} and similar organizational labels at the specified line numbers (119, 140, 150, 175, 188, 229, 270, 294, 350, 370, 392) as they violate the repository's policy against unnecessary code comments in TSX files and should not be included as they provide no functional value or documentation.Source: Coding guidelines
apps/playground/src/app/compare/page.tsx (1)
49-49: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winDrop non-essential JSX section comments in this page.
The section labels are not adding implementation detail and should be removed for consistency with the TSX comment guideline.
As per coding guidelines:
**/*.{ts,tsx}→ “No unnecessary code comments”.Also applies to: 85-85, 117-117, 172-172
🤖 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 `@apps/playground/src/app/compare/page.tsx` at line 49, The file contains non-essential JSX section comments like {/* Hero */} that serve only as organizational labels without providing implementation details, which violates the TSX comment guidelines. Remove all these section label comments found at lines 49, 85, 117, 172 and any similar comments that function only as section markers or organizational dividers, while retaining any comments that explain implementation logic or non-obvious code behavior.Source: Coding guidelines
🤖 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.
Inline comments:
In `@apps/playground/src/lib/comparisons.ts`:
- Around line 89-104: The `US` object in this file has a manually duplicated
`plans` property that mirrors the chat plans from the shared module at
packages/shared/src/chat-plans.ts. This duplication causes pricing updates in
the shared source to not automatically propagate to the comparison page. Import
the chat plans from the shared module instead of hardcoding them, and derive the
`US.plans` property directly from that shared source so that plan updates
automatically sync across the application.
---
Nitpick comments:
In `@apps/playground/src/app/compare/`[slug]/page.tsx:
- Line 119: Remove all unnecessary JSX organizational comments from the page
component in the compare page file. Specifically, delete the section-heading
comments like {/* Hero */}, {/* ... */} and similar organizational labels at the
specified line numbers (119, 140, 150, 175, 188, 229, 270, 294, 350, 370, 392)
as they violate the repository's policy against unnecessary code comments in TSX
files and should not be included as they provide no functional value or
documentation.
In `@apps/playground/src/app/compare/page.tsx`:
- Line 49: The file contains non-essential JSX section comments like {/* Hero
*/} that serve only as organizational labels without providing implementation
details, which violates the TSX comment guidelines. Remove all these section
label comments found at lines 49, 85, 117, 172 and any similar comments that
function only as section markers or organizational dividers, while retaining any
comments that explain implementation logic or non-obvious code behavior.
In `@apps/playground/src/components/compare/comparison-table.tsx`:
- Line 23: Remove the unnecessary JSX section-label comments from the
comparison-table.tsx file. Specifically, delete the comment at line 23 that
reads `{/* Column headers */}` and the similar comment at line 41, as these
purely structural comments do not align with the project's coding guidelines
that prohibit unnecessary code comments in TSX files.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 01b61842-c2ee-4470-b380-54f36380eef7
📒 Files selected for processing (7)
apps/playground/src/app/compare/[slug]/page.tsxapps/playground/src/app/compare/page.tsxapps/playground/src/app/sitemap.tsapps/playground/src/components/compare/comparison-table.tsxapps/playground/src/components/compare/logo-faceoff.tsxapps/playground/src/components/seo/playground-seo-section.tsxapps/playground/src/lib/comparisons.ts
Follow-ups to the comparison pages: - Render real brand SVG logos in the comparison face-offs and table. OpenAI, Anthropic, Google AI and Perplexity reuse the app's existing provider icons; Poe and OpenRouter come from simpleicons.org and the T3 mark from svgl.app (new compare/brand-icons.tsx). Icons with their own background render full-bleed; transparent marks sit tinted on a neutral chip. Drops the placeholder monogram/tileClass data fields. - Make the pages discoverable: add a Pricing · Compare footer link row to the logged-out welcome card (auth-dialog.tsx), which renders on every studio. - Fix the welcome card on mobile: it grew unbounded and pushed the primary CTA off-screen. Rebuilt as a bounded sheet with a scrollable body and a pinned action footer, plus a compact 2-column studio grid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/playground/src/components/compare/logo-faceoff.tsx (1)
102-112: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider removing
classNamefromFaceOffPropsif not needed.
FaceOffPropsextendsTileProps, which includesclassName, but theFaceOffcomponent does not destructure or useclassName. This creates an unused prop. IfFaceOffis not intended to accept custom styling, consider definingFaceOffPropswithout extendingTileProps:interface FaceOffProps { slug: string; competitor: string; size?: number; radius?: number; }Alternatively, if future styling is planned, destructure and apply
classNameto the wrapperdivat line 114.🤖 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 `@apps/playground/src/components/compare/logo-faceoff.tsx` around lines 102 - 112, The `FaceOffProps` interface extends `TileProps` which includes a `className` property, but the `FaceOff` component function does not destructure or use this prop, making it an unused property. Either remove the `TileProps` extension from the `FaceOffProps` interface and define it with only the needed properties (slug, competitor, size, and radius), or if styling support is intended, destructure the `className` parameter in the `FaceOff` function and apply it to the wrapper div element to utilize the inherited property.
🤖 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.
Inline comments:
In `@apps/playground/src/components/compare/logo-faceoff.tsx`:
- Around line 79-82: The initials variable in the logo-faceoff component's
initials generation logic may result in an empty string or a single character if
the competitor name lacks sufficient alphanumeric characters. After the current
chain of operations (replace, slice, toUpperCase) on the competitor string, add
a check to ensure initials has a minimum length or provide a sensible default
fallback value (such as a single character or a placeholder) when the result is
empty or shorter than expected.
---
Nitpick comments:
In `@apps/playground/src/components/compare/logo-faceoff.tsx`:
- Around line 102-112: The `FaceOffProps` interface extends `TileProps` which
includes a `className` property, but the `FaceOff` component function does not
destructure or use this prop, making it an unused property. Either remove the
`TileProps` extension from the `FaceOffProps` interface and define it with only
the needed properties (slug, competitor, size, and radius), or if styling
support is intended, destructure the `className` parameter in the `FaceOff`
function and apply it to the wrapper div element to utilize the inherited
property.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 67e8ff10-e0fb-4a0c-8dea-8dedfda1ea39
📒 Files selected for processing (7)
apps/playground/src/app/compare/[slug]/page.tsxapps/playground/src/app/compare/page.tsxapps/playground/src/components/compare/brand-icons.tsxapps/playground/src/components/compare/comparison-table.tsxapps/playground/src/components/compare/logo-faceoff.tsxapps/playground/src/components/playground/auth-dialog.tsxapps/playground/src/lib/comparisons.ts
💤 Files with no reviewable changes (1)
- apps/playground/src/lib/comparisons.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/playground/src/app/compare/page.tsx
- apps/playground/src/components/compare/comparison-table.tsx
- apps/playground/src/app/compare/[slug]/page.tsx
| const initials = competitor | ||
| .replace(/[^A-Za-z0-9]/g, "") | ||
| .slice(0, 2) | ||
| .toUpperCase(); |
There was a problem hiding this comment.
Consider handling edge cases in the initials fallback.
If competitor contains no alphanumeric characters, initials will be an empty string. If it contains only one alphanumeric character, initials will be a single character. While unlikely for typical competitor names, adding a minimum-length check or default value would make the fallback more robust.
🛡️ Suggested defensive handling
const initials = competitor
.replace(/[^A-Za-z0-9]/g, "")
.slice(0, 2)
.toUpperCase();
+if (initials.length === 0) {
+ // Fallback to first two chars of original competitor name if no alphanumeric
+ return (
+ <div
+ className={cn(
+ "flex shrink-0 items-center justify-center border border-border/60 bg-muted font-bold tracking-tight text-muted-foreground",
+ className,
+ )}
+ style={{
+ width: size,
+ height: size,
+ borderRadius: radius,
+ fontSize: size * 0.32,
+ }}
+ aria-label={competitor}
+ >
+ {competitor.slice(0, 2).toUpperCase() || "??"}
+ </div>
+ );
+}
return (🤖 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 `@apps/playground/src/components/compare/logo-faceoff.tsx` around lines 79 -
82, The initials variable in the logo-faceoff component's initials generation
logic may result in an empty string or a single character if the competitor name
lacks sufficient alphanumeric characters. After the current chain of operations
(replace, slice, toUpperCase) on the competitor string, add a check to ensure
initials has a minimum length or provide a sensible default fallback value (such
as a single character or a placeholder) when the result is empty or shorter than
expected.
- Dynamic OpenGraph images: opengraph-image.tsx for /compare and /compare/[slug] via next/og (shared compare/og-shared.tsx), matching the share-image look. Wire the index twitter:image to the dynamic image too. - Derive US.plans from @llmgateway/shared (CHAT_PLAN_PRICES + CHAT_PLAN_CREDITS_MULTIPLIERS) instead of hardcoding, so pricing changes in the shared source propagate to the comparison pages automatically. - SEO: trim all meta descriptions to <=160 chars (were 188-217 and would truncate in SERPs) and add ItemList schema to the /compare hub. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds an SEO-focused set of comparison pages to the Playground (LLM Gateway Chat) under
/compare, positioning it against the chat apps people evaluate it against:Each
LLM Gateway Chat vs [X]page also targets the[X] alternativekeyword, so one strong page captures both "vs" and "alternative" search intent instead of thin duplicate pages.Why
These are high-intent, bottom-of-funnel keywords ("ChatGPT alternative", "Claude vs …", "Poe alternative"). The pages lean on our real wedge — one subscription, every frontier model, transparent provider-rate credits — while staying honest about where each competitor genuinely wins (ChatGPT's native media, Claude's coding, Gemini's Google integration, Perplexity's cited search, OpenRouter's API economics). Honesty is the conversion strategy on comparison pages; readers verify claims.
How it's built
src/lib/comparisons.tsholds one structured profile per competitor (at-a-glance table, in-depth paragraph sections with a fair "bottom line", who-should-choose-each, an alternative/migration block, and FAQ). Pricing facts mirror@llmgateway/sharedchat plans, so a price change propagates to every page./compare/[slug]withgenerateStaticParams+generateMetadata(title, description, canonical, OpenGraph). Each page emits FAQPage and BreadcrumbList JSON-LD./comparegroups competitors by category with a logo line-up and cards.compare/brand-icons.tsx. ChatGPT/Claude/Gemini/Perplexity reuse the app's existing provider icons; Poe and OpenRouter come from simpleicons.org and the T3 mark from svgl.app. Icons with their own background render full-bleed; transparent marks sit brand-tinted on a neutral chip.Pricing · Compare vs ChatGPT, Claude & morefooter link row on the logged-out welcome card (auth-dialog.tsx), which renders on every studio page. Compare routes are also insitemap.tsand linked from the homePlaygroundSeoSection.Notes on accuracy
Competitor pricing/positioning was researched against current (mid-2026) sources. To stay defensible, pages avoid stating volatile rate-limit caps as official and lean on durable facts (single-vendor lock-in, opaque points/limits, what each tier costs). Easy to refresh later since everything lives in one data file.
Test plan
turbo run build --filter=playgroundpasses;/compare,/compare/[slug], and the logos render (server-rendered).pnpm format/ lint-staged clean.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes