Skip to content

Commit 1a0856a

Browse files
authored
improvement(landing): comparison social cards and links to cloud model providers (#9565)
1 parent 2e6bbb1 commit 1a0856a

5 files changed

Lines changed: 75 additions & 10 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { notFound } from 'next/navigation'
2+
import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image'
3+
import { ALL_COMPETITORS, getCompetitorBySlug } from '@/app/(landing)/comparisons/utils'
4+
5+
export const contentType = 'image/png'
6+
export const size = COVER_OG_SIZE
7+
8+
/** Pre-render every comparison card alongside its page. */
9+
export async function generateStaticParams() {
10+
return ALL_COMPETITORS.map((competitor) => ({ provider: competitor.id }))
11+
}
12+
13+
export default async function Image({ params }: { params: Promise<{ provider: string }> }) {
14+
const { provider } = await params
15+
const competitor = getCompetitorBySlug(provider)
16+
17+
if (!competitor) {
18+
notFound()
19+
}
20+
21+
return createCoverOgImage({
22+
title: `Sim vs ${competitor.name}`,
23+
subtitle: 'Platform, AI, integrations, pricing, security, and support, with sourced facts',
24+
})
25+
}

‎apps/sim/app/(landing)/comparisons/[provider]/page.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ export async function generateMetadata({
7676
'AI agent workspace',
7777
'AI workflow automation comparison',
7878
].join(', '),
79+
routeImage: true,
7980
})
8081
}
8182

‎apps/sim/app/(landing)/models/(shell)/page.tsx‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ChipLink } from '@sim/emcn'
12
import type { Metadata } from 'next'
23
import Link from 'next/link'
34
import type { SearchParams } from 'nuqs/server'
@@ -20,6 +21,7 @@ import {
2021
ALL_CATALOG_MODELS,
2122
getPricingBounds,
2223
MODEL_PROVIDERS_WITH_CATALOGS,
24+
MODEL_RESELLER_PROVIDERS,
2325
TOP_MODEL_PROVIDERS,
2426
TOTAL_MODEL_PROVIDERS,
2527
TOTAL_MODELS,
@@ -273,6 +275,36 @@ export default async function ModelsPage({
273275

274276
<div className='h-px w-full bg-[var(--border)]' />
275277

278+
{MODEL_RESELLER_PROVIDERS.length > 0 && (
279+
<>
280+
<section
281+
id='cloud-providers'
282+
aria-labelledby='cloud-providers-heading'
283+
className='px-6 py-10'
284+
>
285+
<h2
286+
id='cloud-providers-heading'
287+
className='mb-2 text-[24px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] max-lg:text-[20px]'
288+
>
289+
Cloud and inference providers
290+
</h2>
291+
<p className='mb-6 text-[var(--text-muted)] text-sm leading-[150%] tracking-[0.02em]'>
292+
The same models served through cloud platforms and inference hosts, each with
293+
its own pricing and model IDs.
294+
</p>
295+
<nav aria-label='Cloud and inference providers' className='flex flex-wrap gap-2'>
296+
{MODEL_RESELLER_PROVIDERS.map((provider) => (
297+
<ChipLink key={provider.id} href={provider.href} variant='outline'>
298+
{provider.name}
299+
</ChipLink>
300+
))}
301+
</nav>
302+
</section>
303+
304+
<div className='h-px w-full bg-[var(--border)]' />
305+
</>
306+
)}
307+
276308
<section aria-labelledby='all-models-heading'>
277309
<div className='px-6 pt-10 pb-4'>
278310
<h2

‎apps/sim/app/(landing)/models/utils.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,10 @@ export const MODEL_PROVIDERS_WITH_MODELS = MODEL_CATALOG_PROVIDERS.filter(
640640
export const MODEL_PROVIDERS_WITH_CATALOGS = MODEL_CATALOG_PROVIDERS.filter(
641641
(provider) => provider.models.length > 0 && !provider.isReseller
642642
)
643+
/** Cloud platforms and inference hosts that serve other providers' models, each with its own page. */
644+
export const MODEL_RESELLER_PROVIDERS = MODEL_PROVIDERS_WITH_MODELS.filter(
645+
(provider) => provider.isReseller
646+
)
643647
export const MODEL_PROVIDERS_WITH_DYNAMIC_CATALOGS = MODEL_CATALOG_PROVIDERS.filter(
644648
(provider) => provider.models.length === 0
645649
)

‎apps/sim/lib/landing/seo.ts‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ interface LandingMetadataInput {
2323
twitterImageAlt?: string
2424
/** RSS feed path advertised through `<link rel="alternate">`, e.g. "/changelog.xml". */
2525
feedPath?: string
26+
/**
27+
* The route renders its own card from a sibling `opengraph-image`. The shared
28+
* image is then left out, because an explicit `images` entry would replace the
29+
* route's card on X and Slack, where `twitter:image` falls back to it.
30+
*/
31+
routeImage?: boolean
2632
}
2733

2834
/**
@@ -31,9 +37,10 @@ interface LandingMetadataInput {
3137
* landing family, so per-page files declare only what actually varies (title,
3238
* description, keywords, path, and any non-default image alt).
3339
*
34-
* Note: dynamic and image-bearing routes that resolve their card from a sibling
35-
* `opengraph-image` (integrations, models) and the blog family (`buildPostMetadata`
36-
* in `lib/blog/seo`) intentionally do not use this helper.
40+
* Note: the integration and model routes, which resolve their card from a sibling
41+
* `opengraph-image`, and the blog family (`buildPostMetadata` in `lib/blog/seo`)
42+
* intentionally do not use this helper. A route that otherwise fits it but has its
43+
* own card passes `routeImage`.
3744
*/
3845
export function buildLandingMetadata({
3946
title,
@@ -43,6 +50,7 @@ export function buildLandingMetadata({
4350
imageAlt,
4451
twitterImageAlt,
4552
feedPath,
53+
routeImage = false,
4654
}: LandingMetadataInput): Metadata {
4755
const url = `${SITE_URL}${path}`
4856
const ogAlt = imageAlt ?? title
@@ -63,20 +71,15 @@ export function buildLandingMetadata({
6371
url,
6472
siteName: 'Sim',
6573
locale: 'en_US',
66-
images: [
67-
{
68-
...LANDING_SOCIAL_IMAGE,
69-
alt: ogAlt,
70-
},
71-
],
74+
...(routeImage ? {} : { images: [{ ...LANDING_SOCIAL_IMAGE, alt: ogAlt }] }),
7275
},
7376
twitter: {
7477
card: 'summary_large_image',
7578
site: '@simdotai',
7679
creator: '@simdotai',
7780
title,
7881
description,
79-
images: { url: LANDING_SOCIAL_IMAGE.url, alt: twitterAlt },
82+
...(routeImage ? {} : { images: { url: LANDING_SOCIAL_IMAGE.url, alt: twitterAlt } }),
8083
},
8184
alternates: {
8285
canonical: url,

0 commit comments

Comments
 (0)