Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/sim/app/(landing)/comparisons/[provider]/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { notFound } from 'next/navigation'
import { COVER_OG_SIZE, createCoverOgImage } from '@/lib/og/cover-image'
import { ALL_COMPETITORS, getCompetitorBySlug } from '@/app/(landing)/comparisons/utils'

export const contentType = 'image/png'
export const size = COVER_OG_SIZE

/** Pre-render every comparison card alongside its page. */
export async function generateStaticParams() {
return ALL_COMPETITORS.map((competitor) => ({ provider: competitor.id }))
}

export default async function Image({ params }: { params: Promise<{ provider: string }> }) {
const { provider } = await params
const competitor = getCompetitorBySlug(provider)

if (!competitor) {
notFound()
}

return createCoverOgImage({
title: `Sim vs ${competitor.name}`,
subtitle: 'Platform, AI, integrations, pricing, security, and support, with sourced facts',
})
}
1 change: 1 addition & 0 deletions apps/sim/app/(landing)/comparisons/[provider]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function generateMetadata({
'AI agent workspace',
'AI workflow automation comparison',
].join(', '),
routeImage: true,
})
}

Expand Down
32 changes: 32 additions & 0 deletions apps/sim/app/(landing)/models/(shell)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ChipLink } from '@sim/emcn'
import type { Metadata } from 'next'
import Link from 'next/link'
import type { SearchParams } from 'nuqs/server'
Expand All @@ -20,6 +21,7 @@ import {
ALL_CATALOG_MODELS,
getPricingBounds,
MODEL_PROVIDERS_WITH_CATALOGS,
MODEL_RESELLER_PROVIDERS,
TOP_MODEL_PROVIDERS,
TOTAL_MODEL_PROVIDERS,
TOTAL_MODELS,
Expand Down Expand Up @@ -273,6 +275,36 @@ export default async function ModelsPage({

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

{MODEL_RESELLER_PROVIDERS.length > 0 && (
<>
<section
id='cloud-providers'
aria-labelledby='cloud-providers-heading'
className='px-6 py-10'
>
<h2
id='cloud-providers-heading'
className='mb-2 text-[24px] text-[var(--text-primary)] leading-[100%] tracking-[-0.02em] max-lg:text-[20px]'
Comment thread
waleedlatif1 marked this conversation as resolved.
Resolved
>
Cloud and inference providers
</h2>
<p className='mb-6 text-[var(--text-muted)] text-sm leading-[150%] tracking-[0.02em]'>
The same models served through cloud platforms and inference hosts, each with
its own pricing and model IDs.
</p>
<nav aria-label='Cloud and inference providers' className='flex flex-wrap gap-2'>
{MODEL_RESELLER_PROVIDERS.map((provider) => (
<ChipLink key={provider.id} href={provider.href} variant='outline'>
{provider.name}
</ChipLink>
))}
</nav>
</section>

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

<section aria-labelledby='all-models-heading'>
<div className='px-6 pt-10 pb-4'>
<h2
Expand Down
4 changes: 4 additions & 0 deletions apps/sim/app/(landing)/models/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,10 @@ export const MODEL_PROVIDERS_WITH_MODELS = MODEL_CATALOG_PROVIDERS.filter(
export const MODEL_PROVIDERS_WITH_CATALOGS = MODEL_CATALOG_PROVIDERS.filter(
(provider) => provider.models.length > 0 && !provider.isReseller
)
/** Cloud platforms and inference hosts that serve other providers' models, each with its own page. */
export const MODEL_RESELLER_PROVIDERS = MODEL_PROVIDERS_WITH_MODELS.filter(
(provider) => provider.isReseller
)
export const MODEL_PROVIDERS_WITH_DYNAMIC_CATALOGS = MODEL_CATALOG_PROVIDERS.filter(
(provider) => provider.models.length === 0
)
Expand Down
23 changes: 13 additions & 10 deletions apps/sim/lib/landing/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ interface LandingMetadataInput {
twitterImageAlt?: string
/** RSS feed path advertised through `<link rel="alternate">`, e.g. "/changelog.xml". */
feedPath?: string
/**
* The route renders its own card from a sibling `opengraph-image`. The shared
* image is then left out, because an explicit `images` entry would replace the
* route's card on X and Slack, where `twitter:image` falls back to it.
*/
routeImage?: boolean
}

/**
Expand All @@ -31,9 +37,10 @@ interface LandingMetadataInput {
* landing family, so per-page files declare only what actually varies (title,
* description, keywords, path, and any non-default image alt).
*
* Note: dynamic and image-bearing routes that resolve their card from a sibling
* `opengraph-image` (integrations, models) and the blog family (`buildPostMetadata`
* in `lib/blog/seo`) intentionally do not use this helper.
* Note: the integration and model routes, which resolve their card from a sibling
* `opengraph-image`, and the blog family (`buildPostMetadata` in `lib/blog/seo`)
* intentionally do not use this helper. A route that otherwise fits it but has its
* own card passes `routeImage`.
*/
export function buildLandingMetadata({
title,
Expand All @@ -43,6 +50,7 @@ export function buildLandingMetadata({
imageAlt,
twitterImageAlt,
feedPath,
routeImage = false,
}: LandingMetadataInput): Metadata {
const url = `${SITE_URL}${path}`
const ogAlt = imageAlt ?? title
Expand All @@ -63,20 +71,15 @@ export function buildLandingMetadata({
url,
siteName: 'Sim',
locale: 'en_US',
images: [
{
...LANDING_SOCIAL_IMAGE,
alt: ogAlt,
},
],
...(routeImage ? {} : { images: [{ ...LANDING_SOCIAL_IMAGE, alt: ogAlt }] }),
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title,
description,
images: { url: LANDING_SOCIAL_IMAGE.url, alt: twitterAlt },
...(routeImage ? {} : { images: { url: LANDING_SOCIAL_IMAGE.url, alt: twitterAlt } }),
},
alternates: {
canonical: url,
Expand Down
Loading