Skip to content

Commit cbf0a13

Browse files
authored
fix(seo): correct canonical URLs, compress oversized images, add cache 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
1 parent 751eeac commit cbf0a13

File tree

41 files changed

+243
-93
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+243
-93
lines changed

apps/sim/app/(landing)/blog/authors/[id]/page.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from 'next'
22
import Image from 'next/image'
33
import Link from 'next/link'
44
import { getAllPostMeta } from '@/lib/blog/registry'
5+
import { SITE_URL } from '@/lib/core/utils/urls'
56

67
export const revalidate = 3600
78

@@ -17,11 +18,11 @@ export async function generateMetadata({
1718
return {
1819
title: `${name} — Sim Blog`,
1920
description: `Read articles by ${name} on the Sim blog.`,
20-
alternates: { canonical: `https://sim.ai/blog/authors/${id}` },
21+
alternates: { canonical: `${SITE_URL}/blog/authors/${id}` },
2122
openGraph: {
2223
title: `${name} — Sim Blog`,
2324
description: `Read articles by ${name} on the Sim blog.`,
24-
url: `https://sim.ai/blog/authors/${id}`,
25+
url: `${SITE_URL}/blog/authors/${id}`,
2526
siteName: 'Sim',
2627
type: 'profile',
2728
...(author?.avatarUrl
@@ -55,25 +56,25 @@ export default async function AuthorPage({ params }: { params: Promise<{ id: str
5556
{
5657
'@type': 'Person',
5758
name: author.name,
58-
url: `https://sim.ai/blog/authors/${author.id}`,
59+
url: `${SITE_URL}/blog/authors/${author.id}`,
5960
sameAs: author.url ? [author.url] : [],
6061
image: author.avatarUrl,
6162
worksFor: {
6263
'@type': 'Organization',
6364
name: 'Sim',
64-
url: 'https://sim.ai',
65+
url: SITE_URL,
6566
},
6667
},
6768
{
6869
'@type': 'BreadcrumbList',
6970
itemListElement: [
70-
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://sim.ai' },
71-
{ '@type': 'ListItem', position: 2, name: 'Blog', item: 'https://sim.ai/blog' },
71+
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
72+
{ '@type': 'ListItem', position: 2, name: 'Blog', item: `${SITE_URL}/blog` },
7273
{
7374
'@type': 'ListItem',
7475
position: 3,
7576
name: author.name,
76-
item: `https://sim.ai/blog/authors/${author.id}`,
77+
item: `${SITE_URL}/blog/authors/${author.id}`,
7778
},
7879
],
7980
},

apps/sim/app/(landing)/blog/layout.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getNavBlogPosts } from '@/lib/blog/registry'
2+
import { SITE_URL } from '@/lib/core/utils/urls'
23
import Footer from '@/app/(landing)/components/footer/footer'
34
import Navbar from '@/app/(landing)/components/navbar/navbar'
45

@@ -8,10 +9,10 @@ export default async function StudioLayout({ children }: { children: React.React
89
'@context': 'https://schema.org',
910
'@type': 'Organization',
1011
name: 'Sim',
11-
url: 'https://sim.ai',
12+
url: SITE_URL,
1213
description:
1314
'Sim is the open-source AI workspace where teams build, deploy, and manage AI agents.',
14-
logo: 'https://sim.ai/logo/primary/small.png',
15+
logo: `${SITE_URL}/logo/primary/small.png`,
1516
sameAs: [
1617
'https://x.com/simdotai',
1718
'https://github.com/simstudioai/sim',
@@ -23,7 +24,7 @@ export default async function StudioLayout({ children }: { children: React.React
2324
'@context': 'https://schema.org',
2425
'@type': 'WebSite',
2526
name: 'Sim',
26-
url: 'https://sim.ai',
27+
url: SITE_URL,
2728
}
2829

2930
return (

apps/sim/app/(landing)/blog/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Link from 'next/link'
44
import { Badge } from '@/components/emcn'
55
import { getAllPostMeta } from '@/lib/blog/registry'
66
import { buildCollectionPageJsonLd } from '@/lib/blog/seo'
7+
import { SITE_URL } from '@/lib/core/utils/urls'
78

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

3132
return {
3233
title,
@@ -41,7 +42,7 @@ export async function generateMetadata({
4142
type: 'website',
4243
images: [
4344
{
44-
url: 'https://sim.ai/logo/primary/medium.png',
45+
url: `${SITE_URL}/logo/primary/medium.png`,
4546
width: 1200,
4647
height: 630,
4748
alt: 'Sim Blog',

apps/sim/app/(landing)/blog/rss.xml/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { NextResponse } from 'next/server'
22
import { getAllPostMeta } from '@/lib/blog/registry'
3+
import { SITE_URL } from '@/lib/core/utils/urls'
34

45
export const revalidate = 3600
56

67
export async function GET() {
78
const posts = await getAllPostMeta()
89
const items = posts.slice(0, 50)
9-
const site = 'https://sim.ai'
10+
const site = SITE_URL
1011
const lastBuildDate =
1112
items.length > 0 ? new Date(items[0].date).toUTCString() : new Date().toUTCString()
1213

apps/sim/app/(landing)/blog/sitemap-images.xml/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { NextResponse } from 'next/server'
22
import { getAllPostMeta } from '@/lib/blog/registry'
3+
import { SITE_URL } from '@/lib/core/utils/urls'
34

45
export const revalidate = 3600
56

67
export async function GET() {
78
const posts = await getAllPostMeta()
8-
const base = 'https://sim.ai'
9+
const base = SITE_URL
910
const xml = `<?xml version="1.0" encoding="UTF-8"?>
1011
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
1112
${posts

apps/sim/app/(landing)/blog/tags/page.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import type { Metadata } from 'next'
22
import Link from 'next/link'
33
import { getAllTags } from '@/lib/blog/registry'
4+
import { SITE_URL } from '@/lib/core/utils/urls'
45

56
export const metadata: Metadata = {
67
title: 'Tags',
78
description: 'Browse Sim blog posts by topic — AI agents, workflows, integrations, and more.',
8-
alternates: { canonical: 'https://sim.ai/blog/tags' },
9+
alternates: { canonical: `${SITE_URL}/blog/tags` },
910
openGraph: {
1011
title: 'Blog Tags | Sim',
1112
description: 'Browse Sim blog posts by topic — AI agents, workflows, integrations, and more.',
12-
url: 'https://sim.ai/blog/tags',
13+
url: `${SITE_URL}/blog/tags`,
1314
siteName: 'Sim',
1415
locale: 'en_US',
1516
type: 'website',
@@ -26,9 +27,9 @@ const breadcrumbJsonLd = {
2627
'@context': 'https://schema.org',
2728
'@type': 'BreadcrumbList',
2829
itemListElement: [
29-
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://sim.ai' },
30-
{ '@type': 'ListItem', position: 2, name: 'Blog', item: 'https://sim.ai/blog' },
31-
{ '@type': 'ListItem', position: 3, name: 'Tags', item: 'https://sim.ai/blog/tags' },
30+
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
31+
{ '@type': 'ListItem', position: 2, name: 'Blog', item: `${SITE_URL}/blog` },
32+
{ '@type': 'ListItem', position: 3, name: 'Tags', item: `${SITE_URL}/blog/tags` },
3233
],
3334
}
3435

apps/sim/app/(landing)/components/structured-data.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { SITE_URL } from '@/lib/core/utils/urls'
2+
13
/**
24
* JSON-LD structured data for the landing page.
35
*
@@ -23,22 +25,22 @@ export default function StructuredData() {
2325
'@graph': [
2426
{
2527
'@type': 'Organization',
26-
'@id': 'https://sim.ai/#organization',
28+
'@id': `${SITE_URL}/#organization`,
2729
name: 'Sim',
2830
alternateName: 'Sim Studio',
2931
description:
3032
'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.',
31-
url: 'https://sim.ai',
33+
url: SITE_URL,
3234
logo: {
3335
'@type': 'ImageObject',
34-
'@id': 'https://sim.ai/#logo',
35-
url: 'https://sim.ai/logo/b%26w/text/b%26w.svg',
36-
contentUrl: 'https://sim.ai/logo/b%26w/text/b%26w.svg',
36+
'@id': `${SITE_URL}/#logo`,
37+
url: `${SITE_URL}/logo/b%26w/text/b%26w.svg`,
38+
contentUrl: `${SITE_URL}/logo/b%26w/text/b%26w.svg`,
3739
width: 49.78314,
3840
height: 24.276,
3941
caption: 'Sim Logo',
4042
},
41-
image: { '@id': 'https://sim.ai/#logo' },
43+
image: { '@id': `${SITE_URL}/#logo` },
4244
sameAs: [
4345
'https://x.com/simdotai',
4446
'https://github.com/simstudioai/sim',
@@ -53,52 +55,50 @@ export default function StructuredData() {
5355
},
5456
{
5557
'@type': 'WebSite',
56-
'@id': 'https://sim.ai/#website',
57-
url: 'https://sim.ai',
58+
'@id': `${SITE_URL}/#website`,
59+
url: SITE_URL,
5860
name: 'Sim — The AI Workspace | Build, Deploy & Manage AI Agents',
5961
description:
6062
'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.',
61-
publisher: { '@id': 'https://sim.ai/#organization' },
63+
publisher: { '@id': `${SITE_URL}/#organization` },
6264
inLanguage: 'en-US',
6365
},
6466
{
6567
'@type': 'WebPage',
66-
'@id': 'https://sim.ai/#webpage',
67-
url: 'https://sim.ai',
68+
'@id': `${SITE_URL}/#webpage`,
69+
url: SITE_URL,
6870
name: 'Sim — The AI Workspace | Build, Deploy & Manage AI Agents',
69-
isPartOf: { '@id': 'https://sim.ai/#website' },
70-
about: { '@id': 'https://sim.ai/#software' },
71+
isPartOf: { '@id': `${SITE_URL}/#website` },
72+
about: { '@id': `${SITE_URL}/#software` },
7173
datePublished: '2024-01-01T00:00:00+00:00',
7274
dateModified: new Date().toISOString(),
7375
description:
7476
'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.',
75-
breadcrumb: { '@id': 'https://sim.ai/#breadcrumb' },
77+
breadcrumb: { '@id': `${SITE_URL}/#breadcrumb` },
7678
inLanguage: 'en-US',
7779
speakable: {
7880
'@type': 'SpeakableSpecification',
7981
cssSelector: ['#hero-heading', '[id="hero"] p'],
8082
},
81-
potentialAction: [{ '@type': 'ReadAction', target: ['https://sim.ai'] }],
83+
potentialAction: [{ '@type': 'ReadAction', target: [SITE_URL] }],
8284
},
8385
{
8486
'@type': 'BreadcrumbList',
85-
'@id': 'https://sim.ai/#breadcrumb',
86-
itemListElement: [
87-
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://sim.ai' },
88-
],
87+
'@id': `${SITE_URL}/#breadcrumb`,
88+
itemListElement: [{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL }],
8989
},
9090
{
9191
'@type': 'WebApplication',
92-
'@id': 'https://sim.ai/#software',
93-
url: 'https://sim.ai',
92+
'@id': `${SITE_URL}/#software`,
93+
url: SITE_URL,
9494
name: 'Sim — The AI Workspace',
9595
description:
9696
'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.',
9797
applicationCategory: 'BusinessApplication',
9898
applicationSubCategory: 'AI Workspace',
9999
operatingSystem: 'Web',
100100
browserRequirements: 'Requires a modern browser with JavaScript enabled',
101-
installUrl: 'https://sim.ai/signup',
101+
installUrl: `${SITE_URL}/signup`,
102102
offers: [
103103
{
104104
'@type': 'Offer',
@@ -175,16 +175,16 @@ export default function StructuredData() {
175175
},
176176
{
177177
'@type': 'SoftwareSourceCode',
178-
'@id': 'https://sim.ai/#source',
178+
'@id': `${SITE_URL}/#source`,
179179
codeRepository: 'https://github.com/simstudioai/sim',
180180
programmingLanguage: ['TypeScript', 'Python'],
181181
runtimePlatform: 'Node.js',
182182
license: 'https://opensource.org/licenses/Apache-2.0',
183-
isPartOf: { '@id': 'https://sim.ai/#software' },
183+
isPartOf: { '@id': `${SITE_URL}/#software` },
184184
},
185185
{
186186
'@type': 'FAQPage',
187-
'@id': 'https://sim.ai/#faq',
187+
'@id': `${SITE_URL}/#faq`,
188188
mainEntity: [
189189
{
190190
'@type': 'Question',

apps/sim/app/(landing)/integrations/[slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Metadata } from 'next'
22
import Image from 'next/image'
33
import Link from 'next/link'
44
import { notFound } from 'next/navigation'
5-
import { getBaseUrl } from '@/lib/core/utils/urls'
5+
import { SITE_URL } from '@/lib/core/utils/urls'
66
import { IntegrationCtaButton } from '@/app/(landing)/integrations/[slug]/components/integration-cta-button'
77
import { IntegrationFAQ } from '@/app/(landing)/integrations/[slug]/components/integration-faq'
88
import { TemplateCardButton } from '@/app/(landing)/integrations/[slug]/components/template-card-button'
@@ -14,7 +14,7 @@ import { TEMPLATES } from '@/app/workspace/[workspaceId]/home/components/templat
1414

1515
const allIntegrations = integrations as Integration[]
1616
const INTEGRATION_COUNT = allIntegrations.length
17-
const baseUrl = getBaseUrl()
17+
const baseUrl = SITE_URL
1818

1919
/** Fast O(1) lookups — avoids repeated linear scans inside render loops. */
2020
const bySlug = new Map(allIntegrations.map((i) => [i.slug, i]))

apps/sim/app/(landing)/integrations/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { getNavBlogPosts } from '@/lib/blog/registry'
2-
import { getBaseUrl } from '@/lib/core/utils/urls'
2+
import { SITE_URL } from '@/lib/core/utils/urls'
33
import Footer from '@/app/(landing)/components/footer/footer'
44
import Navbar from '@/app/(landing)/components/navbar/navbar'
55

66
export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) {
77
const blogPosts = await getNavBlogPosts()
8-
const url = getBaseUrl()
8+
const url = SITE_URL
99
const orgJsonLd = {
1010
'@context': 'https://schema.org',
1111
'@type': 'Organization',

apps/sim/app/(landing)/integrations/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Metadata } from 'next'
22
import { Badge } from '@/components/emcn'
3-
import { getBaseUrl } from '@/lib/core/utils/urls'
3+
import { SITE_URL } from '@/lib/core/utils/urls'
44
import { IntegrationCard } from './components/integration-card'
55
import { IntegrationGrid } from './components/integration-grid'
66
import { RequestIntegrationModal } from './components/request-integration-modal'
@@ -18,7 +18,7 @@ const INTEGRATION_COUNT = allIntegrations.length
1818
*/
1919
const TOP_NAMES = [...new Set(POPULAR_WORKFLOWS.flatMap((p) => [p.from, p.to]))].slice(0, 6)
2020

21-
const baseUrl = getBaseUrl()
21+
const baseUrl = SITE_URL
2222

2323
/** Curated featured integrations — high-recognition services shown as cards. */
2424
const FEATURED_SLUGS = ['slack', 'notion', 'github', 'gmail'] as const

0 commit comments

Comments
 (0)