refactor(landing): DRY page metadata via buildLandingMetadata + derive pricing credits (#5299)

* refactor(landing): buildLandingMetadata helper + derive pricing credit cells

- Add lib/landing/seo.ts buildLandingMetadata(), the single source of truth for
  the shared landing OpenGraph/Twitter/robots/canonical chrome; migrate 13 static
  landing pages to it (drop ~470 lines of duplicated literals). Output is identical
  per page (verified: rendered head tags unchanged, render mode unchanged); partners
  additionally gains the standard authors/creator/publisher it was missing.
- Drop the now-dead PAGE_URL const + SITE_URL import from the 12 migrated pages that
  no longer reference them (partners keeps both for its JSON-LD).
- comparison-data.ts: derive monthly/daily credit cells from CREDIT_TIERS,
  DEFAULT_FREE_CREDITS, DAILY_REFRESH_RATE and a new CREDITS_PER_DOLLAR constant
  instead of hardcoding, so they can't drift (values byte-identical).

* refactor(billing): bind comparison columns to credit tiers by name

Look up Pro/Max tiers by name instead of array position so the comparison
table can't silently bind to the wrong tier if CREDIT_TIERS is reordered or a
tier is inserted (addresses Greptile P2). Values unchanged.
This commit is contained in:
Waleed
2026-06-30 16:44:55 -07:00
committed by GitHub
parent c17c003a91
commit 8c8e4b759e
16 changed files with 206 additions and 600 deletions
+5 -48
View File
@@ -1,60 +1,17 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import Changelog from '@/app/(landing)/changelog/changelog'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/changelog`
const TITLE = 'Changelog | Sim, the AI Workspace'
const DESCRIPTION =
'Every new feature, improvement, and fix in Sim, the open-source AI workspace, with release notes straight from GitHub.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'Changelog | Sim, the AI Workspace',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: {
url: '/logo/426-240/reverse/small.png',
alt: 'Changelog | Sim, the AI Workspace',
},
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
path: '/changelog',
})
export default function Page() {
return <Changelog />
+5 -48
View File
@@ -1,60 +1,17 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import Demo from '@/app/(landing)/demo/demo'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/demo`
const TITLE = 'Book a Demo | Sim, the AI Workspace'
const DESCRIPTION =
'Book a demo of Sim, the AI agent workspace where teams build, deploy, and manage AI agents and workflows that connect 1,000+ integrations and every major LLM.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'Book a Demo | Sim, the AI Workspace',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: {
url: '/logo/426-240/reverse/small.png',
alt: 'Book a Demo | Sim, the AI Workspace',
},
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
path: '/demo',
})
export default function Page() {
return <Demo />
+6 -45
View File
@@ -1,59 +1,20 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import EnterprisePage from '@/app/(landing)/enterprise/enterprise'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/enterprise`
const TITLE = 'Enterprise AI Agent Platform | Sim AI'
const DESCRIPTION =
'Build, deploy, and govern enterprise AI agents in one workspace with security, approvals, observability, and collaboration.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/enterprise',
keywords:
'enterprise ai agents, enterprise ai agent, enterprise ai agent platform, enterprise workflow agents',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: TITLE,
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
export default function Page() {
return <EnterprisePage />
+6 -40
View File
@@ -1,54 +1,20 @@
import { ChipLink } from '@sim/emcn'
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
const PAGE_URL = `${SITE_URL}/partners`
const TITLE = 'Partner Program | Sim'
const DESCRIPTION =
"Join the Sim partner program. Build, deploy, and sell AI agent solutions powered by Sim's AI workspace, and earn your certification through Sim Academy."
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/partners',
keywords:
'Sim partner program, AI agent partners, AI workspace partners, Sim Academy certification, AI agent reseller, co-marketing',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: TITLE,
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
const partnersJsonLd = {
'@context': 'https://schema.org',
+5 -48
View File
@@ -1,64 +1,21 @@
import type { Metadata } from 'next'
import type { SearchParams } from 'nuqs/server'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import Pricing from '@/app/(landing)/pricing/pricing'
import { pricingSearchParamsCache } from '@/app/(landing)/pricing/search-params'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/pricing`
const TITLE = 'Pricing | Sim, the AI Workspace'
const DESCRIPTION =
'Pricing for Sim, the open-source AI workspace for building, deploying, and managing AI agents. Compare the Free, Pro, Max, and Enterprise plans. Start free.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/pricing',
keywords:
'Sim pricing, AI workspace pricing, AI agent platform pricing, build AI agents, Pro plan, Max plan, Enterprise plan, open-source AI agents, LLM pricing',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'Pricing | Sim, the AI Workspace',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: {
url: '/logo/426-240/reverse/small.png',
alt: 'Pricing | Sim, the AI Workspace',
},
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
})
export default async function Page({ searchParams }: { searchParams: Promise<SearchParams> }) {
await pricingSearchParamsCache.parse(searchParams)
+5 -48
View File
@@ -1,60 +1,17 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import Privacy from '@/app/(landing)/privacy/privacy'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/privacy`
const TITLE = 'Privacy Policy | Sim, the AI Workspace'
const DESCRIPTION =
'How Sim, the open-source AI workspace, collects, uses, and protects your data, including data obtained from Google APIs, and the controls you have over it.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'Privacy Policy | Sim, the AI Workspace',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: {
url: '/logo/426-240/reverse/small.png',
alt: 'Privacy Policy | Sim, the AI Workspace',
},
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
path: '/privacy',
})
export default function Page() {
return <Privacy />
@@ -1,59 +1,20 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import ComplianceSolution from '@/app/(landing)/solutions/compliance/compliance'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/solutions/compliance`
const TITLE = 'AI Agents for Continuous Compliance & Audit | Sim'
const DESCRIPTION =
'Compliance teams use Sim, the open-source AI workspace, to build and deploy AI agents that automate evidence collection, control monitoring, and reporting.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/solutions/compliance',
keywords:
'AI workspace, AI agents for compliance, compliance automation, evidence collection, control monitoring, audit readiness, open-source AI agent platform',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'AI Agents for Continuous Compliance & Audit | Sim',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
export default function Page() {
return <ComplianceSolution />
@@ -1,59 +1,20 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import EngineeringSolution from '@/app/(landing)/solutions/engineering/engineering'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/solutions/engineering`
const TITLE = 'AI Agents for Code Review & On-Call | Sim'
const DESCRIPTION =
'Engineering teams use Sim, the open-source AI workspace, to build and deploy AI agents that automate code review, on-call triage, and documentation.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/solutions/engineering',
keywords:
'AI workspace, AI agents for engineering, automated code review, on-call automation, CI/CD agents, developer automation, open-source AI agent platform',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'AI Agents for Code Review & On-Call | Sim',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
export default function Page() {
return <EngineeringSolution />
@@ -1,59 +1,20 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import FinanceSolution from '@/app/(landing)/solutions/finance/finance'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/solutions/finance`
const TITLE = 'AI Agents for Invoice Processing & Reconciliation | Sim'
const DESCRIPTION =
'Finance teams use Sim, the open-source AI workspace, to build and deploy AI agents that automate reconciliation, invoice processing, and reporting.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/solutions/finance',
keywords:
'AI workspace, AI agents for finance, finance automation, invoice processing, account reconciliation, financial reporting, open-source AI agent platform',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'AI Agents for Invoice Processing & Reconciliation | Sim',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
export default function Page() {
return <FinanceSolution />
+6 -45
View File
@@ -1,59 +1,20 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import HrSolution from '@/app/(landing)/solutions/hr/hr'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/solutions/hr`
const TITLE = 'AI Agents for Onboarding & People Operations | Sim'
const DESCRIPTION =
'HR teams use Sim, the open-source AI workspace, to build and deploy AI agents that automate onboarding, employee questions, and approvals.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/solutions/hr',
keywords:
'AI workspace, AI agents for HR, HR automation, employee onboarding, people operations, HRIS automation, open-source AI agent platform',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'AI Agents for Onboarding & People Operations | Sim',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
export default function Page() {
return <HrSolution />
+6 -45
View File
@@ -1,59 +1,20 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import ItSolution from '@/app/(landing)/solutions/it/it'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/solutions/it`
const TITLE = 'AI Agents for Ticket Triage & Access | Sim'
const DESCRIPTION =
'IT teams use Sim, the open-source AI workspace, to build and deploy AI agents that automate ticket triage, access provisioning, and monitoring.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/solutions/it',
keywords:
'AI workspace, IT automation, AI agents for IT, IT service desk automation, access provisioning, infrastructure monitoring, open-source AI agent platform',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'AI Agents for Ticket Triage & Access | Sim',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: { url: '/logo/426-240/reverse/small.png', alt: 'Sim' },
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
twitterImageAlt: 'Sim',
})
export default function Page() {
return <ItSolution />
+5 -48
View File
@@ -1,60 +1,17 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import Terms from '@/app/(landing)/terms/terms'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/terms`
const TITLE = 'Terms of Service | Sim, the AI Workspace'
const DESCRIPTION =
'The terms and conditions for using Sim, the open-source AI workspace: subscription plans, data ownership, acceptable use, and your rights.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'Terms of Service | Sim, the AI Workspace',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: {
url: '/logo/426-240/reverse/small.png',
alt: 'Terms of Service | Sim, the AI Workspace',
},
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
path: '/terms',
})
export default function Page() {
return <Terms />
+5 -48
View File
@@ -1,62 +1,19 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { buildLandingMetadata } from '@/lib/landing/seo'
import Workflows from '@/app/(landing)/workflows/workflows'
export const revalidate = 3600
const PAGE_URL = `${SITE_URL}/workflows`
const TITLE = 'Workflows | The Visual Builder in Sim, the AI Workspace'
const DESCRIPTION =
'Workflows is the visual builder in Sim, the open-source AI workspace. Connect blocks, every major LLM, and 1,000+ integrations into agent logic.'
export const metadata: Metadata = {
metadataBase: new URL(SITE_URL),
title: { absolute: TITLE },
export const metadata = buildLandingMetadata({
title: TITLE,
description: DESCRIPTION,
path: '/workflows',
keywords:
'AI workspace, visual workflow builder, build AI agents, AI agent workflow builder, LLM orchestration, AI integrations, open-source AI agent platform, agentic workflows',
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title: TITLE,
description: DESCRIPTION,
type: 'website',
url: PAGE_URL,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: '/logo/426-240/reverse/small.png',
width: 2130,
height: 1200,
alt: 'Workflows | The Visual Builder in Sim, the AI Workspace',
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title: TITLE,
description: DESCRIPTION,
images: {
url: '/logo/426-240/reverse/small.png',
alt: 'Workflows | The Visual Builder in Sim, the AI Workspace',
},
},
alternates: {
canonical: PAGE_URL,
languages: { 'en-US': PAGE_URL, 'x-default': PAGE_URL },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
})
export default function Page() {
return <Workflows />
@@ -1,3 +1,30 @@
import {
CREDIT_TIERS,
CREDITS_PER_DOLLAR,
DAILY_REFRESH_RATE,
DEFAULT_FREE_CREDITS,
} from '@/lib/billing/constants'
/**
* Looks up a credit tier by name, so a column binds to the right tier even if
* {@link CREDIT_TIERS} is reordered or a tier is inserted.
*/
function tierByName(name: (typeof CREDIT_TIERS)[number]['name']) {
const tier = CREDIT_TIERS.find((t) => t.name === name)
if (!tier) throw new Error(`comparison-data: no credit tier named "${name}"`)
return tier
}
const PRO_TIER = tierByName('Pro')
const MAX_TIER = tierByName('Max')
/** Formats a credit count with thousands separators (e.g. 25000 → "25,000"). */
const formatCredits = (credits: number): string => credits.toLocaleString('en-US')
/** Daily refresh credits for a plan: 1% of plan dollars/day, in credits. */
const dailyRefreshCredits = (dollars: number): number =>
Math.round(dollars * DAILY_REFRESH_RATE * CREDITS_PER_DOLLAR)
/** A brand icon rendered in a cell instead of a check/em-dash/text. */
export interface CellIcon {
/** Icon identifier resolved to a component by the table renderer. */
@@ -58,11 +85,21 @@ export const COMPARISON_SECTIONS: ComparisonSection[] = [
rows: [
{
label: 'Monthly credits',
values: ['1,000', '6,000', '25,000', 'Custom'],
values: [
formatCredits(DEFAULT_FREE_CREDITS * CREDITS_PER_DOLLAR),
formatCredits(PRO_TIER.credits),
formatCredits(MAX_TIER.credits),
'Custom',
],
},
{
label: 'Daily refresh',
values: [false, '+50', '+200', 'Custom'],
values: [
false,
`+${formatCredits(dailyRefreshCredits(PRO_TIER.dollars))}`,
`+${formatCredits(dailyRefreshCredits(MAX_TIER.dollars))}`,
'Custom',
],
},
],
},
+6
View File
@@ -50,6 +50,12 @@ export const CREDIT_TIERS = [
export type CreditTier = (typeof CREDIT_TIERS)[number]
/**
* Credits granted per dollar of plan spend. A credit is $0.005, so a dollar
* buys 200 — the conversion behind both free-tier and daily-refresh credits.
*/
export const CREDITS_PER_DOLLAR = 200
/**
* Daily refresh rate: 1% of plan cost per day.
* E.g. $25 plan => $0.25/day => 50 credits/day included usage.
+89
View File
@@ -0,0 +1,89 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
/** Shared OpenGraph/Twitter card image for landing pages. */
const OG_IMAGE_URL = '/logo/426-240/reverse/small.png'
const OG_IMAGE_WIDTH = 2130
const OG_IMAGE_HEIGHT = 1200
interface LandingMetadataInput {
/** Absolute `<title>`, rendered as-is (no site template applied). */
title: string
description: string
/** Path under the site root, e.g. `/pricing`. Use `''` for the home page. */
path: string
/** Optional comma-separated keywords; omitted from the output when absent. */
keywords?: string
/** OpenGraph image alt text. Defaults to {@link title}. */
imageAlt?: string
/** Twitter card image alt text. Defaults to {@link imageAlt}. */
twitterImageAlt?: string
}
/**
* Builds the canonical metadata for a static landing page — the single source of
* truth for the OpenGraph/Twitter/robots/canonical chrome shared across the
* 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.
*/
export function buildLandingMetadata({
title,
description,
path,
keywords,
imageAlt,
twitterImageAlt,
}: LandingMetadataInput): Metadata {
const url = `${SITE_URL}${path}`
const ogAlt = imageAlt ?? title
const twitterAlt = twitterImageAlt ?? ogAlt
return {
metadataBase: new URL(SITE_URL),
title: { absolute: title },
description,
...(keywords ? { keywords } : {}),
authors: [{ name: 'Sim' }],
creator: 'Sim',
publisher: 'Sim',
openGraph: {
title,
description,
type: 'website',
url,
siteName: 'Sim',
locale: 'en_US',
images: [
{
url: OG_IMAGE_URL,
width: OG_IMAGE_WIDTH,
height: OG_IMAGE_HEIGHT,
alt: ogAlt,
type: 'image/png',
},
],
},
twitter: {
card: 'summary_large_image',
site: '@simdotai',
creator: '@simdotai',
title,
description,
images: { url: OG_IMAGE_URL, alt: twitterAlt },
},
alternates: {
canonical: url,
languages: { 'en-US': url, 'x-default': url },
},
robots: {
index: true,
follow: true,
googleBot: { index: true, follow: true, 'max-image-preview': 'large', 'max-snippet': -1 },
},
category: 'technology',
}
}