Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
fix(seo): correct canonical URLs, compress oversized images, add cach…
…e headers (#4168)

* fix(seo): correct canonical URLs, compress oversized images, add cache headers

- Replace all hardcoded https://sim.ai with https://www.sim.ai via SITE_URL constant
- Migrate models, integrations, and homepage metadata from getBaseUrl() to SITE_URL
- Compress 6 blog/landing images from 2.6MB to 300KB total
- Convert mothership cover from PNG to JPEG (1.1MB → 99KB)
- Add Cache-Control headers for static assets (1d max-age, 7d stale-while-revalidate)
- Add SEO regression test scanning all public pages for canonical URL violations

* fix(seo): replace hardcoded URLs with SITE_URL, broaden test detection

- Replace hardcoded https://www.sim.ai with SITE_URL in academy, changelog.xml, and whitelabeling
- Broaden getBaseUrl() detection in SEO test to match any variable name assignment
- Add ee/whitelabeling/metadata.ts to SEO test scan scope
  • Loading branch information
emir-karabeg authored Apr 14, 2026
commit cbf0a139ed7873571a78d914b2838aa32009e02f
15 changes: 8 additions & 7 deletions apps/sim/app/(landing)/blog/authors/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { getAllPostMeta } from '@/lib/blog/registry'
import { SITE_URL } from '@/lib/core/utils/urls'

export const revalidate = 3600

Expand All @@ -17,11 +18,11 @@ export async function generateMetadata({
return {
title: `${name} — Sim Blog`,
description: `Read articles by ${name} on the Sim blog.`,
alternates: { canonical: `https://sim.ai/blog/authors/${id}` },
alternates: { canonical: `${SITE_URL}/blog/authors/${id}` },
openGraph: {
title: `${name} — Sim Blog`,
description: `Read articles by ${name} on the Sim blog.`,
url: `https://sim.ai/blog/authors/${id}`,
url: `${SITE_URL}/blog/authors/${id}`,
siteName: 'Sim',
type: 'profile',
...(author?.avatarUrl
Expand Down Expand Up @@ -55,25 +56,25 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str
{
'@type': 'Person',
name: author.name,
url: `https://sim.ai/blog/authors/${author.id}`,
url: `${SITE_URL}/blog/authors/${author.id}`,
sameAs: author.url ? [author.url] : [],
image: author.avatarUrl,
worksFor: {
'@type': 'Organization',
name: 'Sim',
url: 'https://sim.ai',
url: SITE_URL,
},
},
{
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://sim.ai' },
{ '@type': 'ListItem', position: 2, name: 'Blog', item: 'https://sim.ai/blog' },
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
{ '@type': 'ListItem', position: 2, name: 'Blog', item: `${SITE_URL}/blog` },
{
'@type': 'ListItem',
position: 3,
name: author.name,
item: `https://sim.ai/blog/authors/${author.id}`,
item: `${SITE_URL}/blog/authors/${author.id}`,
},
],
},
Expand Down
7 changes: 4 additions & 3 deletions apps/sim/app/(landing)/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getNavBlogPosts } from '@/lib/blog/registry'
import { SITE_URL } from '@/lib/core/utils/urls'
import Footer from '@/app/(landing)/components/footer/footer'
import Navbar from '@/app/(landing)/components/navbar/navbar'

Expand All @@ -8,10 +9,10 @@ export default async function StudioLayout({ children }: { children: React.React
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Sim',
url: 'https://sim.ai',
url: SITE_URL,
description:
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents.',
logo: 'https://sim.ai/logo/primary/small.png',
logo: `${SITE_URL}/logo/primary/small.png`,
sameAs: [
'https://x.com/simdotai',
'https://github.com/simstudioai/sim',
Expand All @@ -23,7 +24,7 @@ export default async function StudioLayout({ children }: { children: React.React
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Sim',
url: 'https://sim.ai',
url: SITE_URL,
}

return (
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/app/(landing)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Link from 'next/link'
import { Badge } from '@/components/emcn'
import { getAllPostMeta } from '@/lib/blog/registry'
import { buildCollectionPageJsonLd } from '@/lib/blog/seo'
import { SITE_URL } from '@/lib/core/utils/urls'

export async function generateMetadata({
searchParams,
Expand All @@ -26,7 +27,7 @@ export async function generateMetadata({
if (tag) canonicalParams.set('tag', tag)
if (pageNum > 1) canonicalParams.set('page', String(pageNum))
const qs = canonicalParams.toString()
const canonical = `https://sim.ai/blog${qs ? `?${qs}` : ''}`
const canonical = `${SITE_URL}/blog${qs ? `?${qs}` : ''}`

return {
title,
Expand All @@ -41,7 +42,7 @@ export async function generateMetadata({
type: 'website',
images: [
{
url: 'https://sim.ai/logo/primary/medium.png',
url: `${SITE_URL}/logo/primary/medium.png`,
width: 1200,
height: 630,
alt: 'Sim Blog',
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/(landing)/blog/rss.xml/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { NextResponse } from 'next/server'
import { getAllPostMeta } from '@/lib/blog/registry'
import { SITE_URL } from '@/lib/core/utils/urls'

export const revalidate = 3600

export async function GET() {
const posts = await getAllPostMeta()
const items = posts.slice(0, 50)
const site = 'https://sim.ai'
const site = SITE_URL
const lastBuildDate =
items.length > 0 ? new Date(items[0].date).toUTCString() : new Date().toUTCString()

Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/(landing)/blog/sitemap-images.xml/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NextResponse } from 'next/server'
import { getAllPostMeta } from '@/lib/blog/registry'
import { SITE_URL } from '@/lib/core/utils/urls'

export const revalidate = 3600

export async function GET() {
const posts = await getAllPostMeta()
const base = 'https://sim.ai'
const base = SITE_URL
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
${posts
Expand Down
11 changes: 6 additions & 5 deletions apps/sim/app/(landing)/blog/tags/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import type { Metadata } from 'next'
import Link from 'next/link'
import { getAllTags } from '@/lib/blog/registry'
import { SITE_URL } from '@/lib/core/utils/urls'

export const metadata: Metadata = {
title: 'Tags',
description: 'Browse Sim blog posts by topic — AI agents, workflows, integrations, and more.',
alternates: { canonical: 'https://sim.ai/blog/tags' },
alternates: { canonical: `${SITE_URL}/blog/tags` },
openGraph: {
title: 'Blog Tags | Sim',
description: 'Browse Sim blog posts by topic — AI agents, workflows, integrations, and more.',
url: 'https://sim.ai/blog/tags',
url: `${SITE_URL}/blog/tags`,
siteName: 'Sim',
locale: 'en_US',
type: 'website',
Expand All @@ -26,9 +27,9 @@ const breadcrumbJsonLd = {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://sim.ai' },
{ '@type': 'ListItem', position: 2, name: 'Blog', item: 'https://sim.ai/blog' },
{ '@type': 'ListItem', position: 3, name: 'Tags', item: 'https://sim.ai/blog/tags' },
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
{ '@type': 'ListItem', position: 2, name: 'Blog', item: `${SITE_URL}/blog` },
{ '@type': 'ListItem', position: 3, name: 'Tags', item: `${SITE_URL}/blog/tags` },
],
}

Expand Down
50 changes: 25 additions & 25 deletions apps/sim/app/(landing)/components/structured-data.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SITE_URL } from '@/lib/core/utils/urls'

/**
* JSON-LD structured data for the landing page.
*
Expand All @@ -23,22 +25,22 @@ export default function StructuredData() {
'@graph': [
{
'@type': 'Organization',
'@id': 'https://sim.ai/#organization',
'@id': `${SITE_URL}/#organization`,
name: 'Sim',
alternateName: 'Sim Studio',
description:
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work.',
url: 'https://sim.ai',
url: SITE_URL,
logo: {
'@type': 'ImageObject',
'@id': 'https://sim.ai/#logo',
url: 'https://sim.ai/logo/b%26w/text/b%26w.svg',
contentUrl: 'https://sim.ai/logo/b%26w/text/b%26w.svg',
'@id': `${SITE_URL}/#logo`,
url: `${SITE_URL}/logo/b%26w/text/b%26w.svg`,
contentUrl: `${SITE_URL}/logo/b%26w/text/b%26w.svg`,
width: 49.78314,
height: 24.276,
caption: 'Sim Logo',
},
image: { '@id': 'https://sim.ai/#logo' },
image: { '@id': `${SITE_URL}/#logo` },
sameAs: [
'https://x.com/simdotai',
'https://github.com/simstudioai/sim',
Expand All @@ -53,52 +55,50 @@ export default function StructuredData() {
},
{
'@type': 'WebSite',
'@id': 'https://sim.ai/#website',
url: 'https://sim.ai',
'@id': `${SITE_URL}/#website`,
url: SITE_URL,
name: 'Sim — The AI Workspace | Build, Deploy & Manage AI Agents',
description:
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM. Join 100,000+ builders.',
publisher: { '@id': 'https://sim.ai/#organization' },
publisher: { '@id': `${SITE_URL}/#organization` },
inLanguage: 'en-US',
},
{
'@type': 'WebPage',
'@id': 'https://sim.ai/#webpage',
url: 'https://sim.ai',
'@id': `${SITE_URL}/#webpage`,
url: SITE_URL,
name: 'Sim — The AI Workspace | Build, Deploy & Manage AI Agents',
isPartOf: { '@id': 'https://sim.ai/#website' },
about: { '@id': 'https://sim.ai/#software' },
isPartOf: { '@id': `${SITE_URL}/#website` },
about: { '@id': `${SITE_URL}/#software` },
datePublished: '2024-01-01T00:00:00+00:00',
dateModified: new Date().toISOString(),
description:
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work.',
breadcrumb: { '@id': 'https://sim.ai/#breadcrumb' },
breadcrumb: { '@id': `${SITE_URL}/#breadcrumb` },
inLanguage: 'en-US',
speakable: {
'@type': 'SpeakableSpecification',
cssSelector: ['#hero-heading', '[id="hero"] p'],
},
potentialAction: [{ '@type': 'ReadAction', target: ['https://sim.ai'] }],
potentialAction: [{ '@type': 'ReadAction', target: [SITE_URL] }],
},
{
'@type': 'BreadcrumbList',
'@id': 'https://sim.ai/#breadcrumb',
itemListElement: [
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://sim.ai' },
],
'@id': `${SITE_URL}/#breadcrumb`,
itemListElement: [{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL }],
},
{
'@type': 'WebApplication',
'@id': 'https://sim.ai/#software',
url: 'https://sim.ai',
'@id': `${SITE_URL}/#software`,
url: SITE_URL,
name: 'Sim — The AI Workspace',
description:
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents. Connect 1,000+ integrations and every major LLM to create agents that automate real work — visually, conversationally, or with code. Trusted by over 100,000 builders. SOC2 compliant.',
applicationCategory: 'BusinessApplication',
applicationSubCategory: 'AI Workspace',
operatingSystem: 'Web',
browserRequirements: 'Requires a modern browser with JavaScript enabled',
installUrl: 'https://sim.ai/signup',
installUrl: `${SITE_URL}/signup`,
offers: [
{
'@type': 'Offer',
Expand Down Expand Up @@ -175,16 +175,16 @@ export default function StructuredData() {
},
{
'@type': 'SoftwareSourceCode',
'@id': 'https://sim.ai/#source',
'@id': `${SITE_URL}/#source`,
codeRepository: 'https://github.com/simstudioai/sim',
programmingLanguage: ['TypeScript', 'Python'],
runtimePlatform: 'Node.js',
license: 'https://opensource.org/licenses/Apache-2.0',
isPartOf: { '@id': 'https://sim.ai/#software' },
isPartOf: { '@id': `${SITE_URL}/#software` },
},
{
'@type': 'FAQPage',
'@id': 'https://sim.ai/#faq',
'@id': `${SITE_URL}/#faq`,
mainEntity: [
{
'@type': 'Question',
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/integrations/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Metadata } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { SITE_URL } from '@/lib/core/utils/urls'
import { IntegrationCtaButton } from '@/app/(landing)/integrations/[slug]/components/integration-cta-button'
import { IntegrationFAQ } from '@/app/(landing)/integrations/[slug]/components/integration-faq'
import { TemplateCardButton } from '@/app/(landing)/integrations/[slug]/components/template-card-button'
Expand All @@ -14,7 +14,7 @@ import { TEMPLATES } from '@/app/workspace/[workspaceId]/home/components/templat

const allIntegrations = integrations as Integration[]
const INTEGRATION_COUNT = allIntegrations.length
const baseUrl = getBaseUrl()
const baseUrl = SITE_URL

/** Fast O(1) lookups — avoids repeated linear scans inside render loops. */
const bySlug = new Map(allIntegrations.map((i) => [i.slug, i]))
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/integrations/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { getNavBlogPosts } from '@/lib/blog/registry'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { SITE_URL } from '@/lib/core/utils/urls'
import Footer from '@/app/(landing)/components/footer/footer'
import Navbar from '@/app/(landing)/components/navbar/navbar'

export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) {
const blogPosts = await getNavBlogPosts()
const url = getBaseUrl()
const url = SITE_URL
const orgJsonLd = {
'@context': 'https://schema.org',
'@type': 'Organization',
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/integrations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Metadata } from 'next'
import { Badge } from '@/components/emcn'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { SITE_URL } from '@/lib/core/utils/urls'
import { IntegrationCard } from './components/integration-card'
import { IntegrationGrid } from './components/integration-grid'
import { RequestIntegrationModal } from './components/request-integration-modal'
Expand All @@ -18,7 +18,7 @@ const INTEGRATION_COUNT = allIntegrations.length
*/
const TOP_NAMES = [...new Set(POPULAR_WORKFLOWS.flatMap((p) => [p.from, p.to]))].slice(0, 6)

const baseUrl = getBaseUrl()
const baseUrl = SITE_URL

/** Curated featured integrations — high-recognition services shown as cards. */
const FEATURED_SLUGS = ['slack', 'notion', 'github', 'gmail'] as const
Expand Down
3 changes: 2 additions & 1 deletion apps/sim/app/(landing)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { Metadata } from 'next'
import { SITE_URL } from '@/lib/core/utils/urls'
import { martianMono } from '@/app/_styles/fonts/martian-mono/martian-mono'
import { season } from '@/app/_styles/fonts/season/season'

export const metadata: Metadata = {
metadataBase: new URL('https://sim.ai'),
metadataBase: new URL(SITE_URL),
manifest: '/manifest.webmanifest',
icons: {
icon: [{ url: '/icon.svg', type: 'image/svg+xml', sizes: 'any' }],
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Metadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { SITE_URL } from '@/lib/core/utils/urls'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
import { FeaturedModelCard, ProviderIcon } from '@/app/(landing)/models/components/model-primitives'
import {
Expand All @@ -18,7 +18,7 @@ import {
getRelatedModels,
} from '@/app/(landing)/models/utils'

const baseUrl = getBaseUrl()
const baseUrl = SITE_URL

export async function generateStaticParams() {
return ALL_CATALOG_MODELS.map((model) => ({
Expand Down Expand Up @@ -221,7 +221,7 @@ export default async function ModelPage({

<div className='flex flex-wrap gap-2'>
<a
href='https://sim.ai'
href='/'
className='inline-flex h-[32px] items-center gap-2 rounded-[5px] border border-white bg-white px-2.5 font-season text-black text-sm transition-colors hover:border-[#E0E0E0] hover:bg-[#E0E0E0]'
>
Build with this model
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/models/[provider]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Metadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import { Badge } from '@/components/emcn'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { SITE_URL } from '@/lib/core/utils/urls'
import { LandingFAQ } from '@/app/(landing)/components/landing-faq'
import {
ChevronArrow,
Expand All @@ -20,7 +20,7 @@ import {
TOP_MODEL_PROVIDERS,
} from '@/app/(landing)/models/utils'

const baseUrl = getBaseUrl()
const baseUrl = SITE_URL

export async function generateStaticParams() {
return MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({
Expand Down
Loading
Loading