Skip to content

Commit d44cee4

Browse files
authored
improvement(landing): serve the homepage as Markdown when clients ask for text/markdown (#9154)
* improvement(landing): serve the homepage as Markdown when clients ask for text/markdown * improvement(landing): describe the Markdown variant's cache policy accurately
1 parent 678c1a4 commit d44cee4

12 files changed

Lines changed: 224 additions & 61 deletions

File tree

‎apps/docs/content/docs/api-reference/getting-started.mdx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Sim Cloud uses `https://www.sim.ai`, with API paths under `/api/v2`. There is no
1414

1515
Download the [OpenAPI 3.1 specification](/openapi.json) for client generation, request validation, and API tooling.
1616

17+
Official SDKs wrap these calls: [TypeScript](/api-reference/typescript) (`bun add simstudio-ts-sdk`) and [Python](/api-reference/python) (`pip install simstudio-sdk`). The [CLI](/cli) (`bun add -g sim`) runs workflows and manages resources from a terminal.
18+
1719
## Run a deployed workflow [#quick-start]
1820

1921
<Steps>

‎apps/docs/lib/redirects.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ const PATH_REDIRECTS: DocsRedirect[] = [
9898
permanent: true,
9999
},
100100
{ source: '/api-reference', destination: '/api-reference/getting-started', permanent: true },
101+
// sdks/* folded into api-reference/
102+
{ source: '/sdks', destination: '/api-reference/getting-started', permanent: true },
103+
{ source: '/sdks/typescript', destination: '/api-reference/typescript', permanent: true },
104+
{ source: '/sdks/python', destination: '/api-reference/python', permanent: true },
101105
// building-agents/agents merged into the building-agents overview
102106
{ source: '/building-agents/agents', destination: '/agents', permanent: true },
103107
// form deployment removed

‎apps/sim/app/(landing)/components/hero/hero.tsx‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { INTEGRATION_COUNT_LABEL } from '@sim/deployment-config/integration-claims'
21
import { cn } from '@sim/emcn'
32
import { HeroAnnouncementChip } from '@/app/(landing)/components/hero/components/hero-announcement-chip'
43
import { LandingHeroHeader } from '@/app/(landing)/components/hero/components/hero-header'
54
import { HeroPlatformStage } from '@/app/(landing)/components/hero/components/hero-platform-stage'
5+
import {
6+
HOME_HERO_HEADING,
7+
HOME_HERO_SUMMARY,
8+
} from '@/app/(landing)/components/home-structured-data'
69
import {
710
HOME_INSET,
811
LANDING_CONTENT_WIDTH,
@@ -30,12 +33,7 @@ export function Hero() {
3033
LANDING_GUTTER
3134
)}
3235
>
33-
<p className='sr-only'>
34-
Sim is the open-source AI workspace where teams build, deploy, and manage AI agents for
35-
their organization. Connect {INTEGRATION_COUNT_LABEL} integrations and every major LLM, then
36-
govern access, spend, data, and deployment from one place. Build visually, conversationally,
37-
or with code, and run Sim in your own cloud.
38-
</p>
36+
<p className='sr-only'>{HOME_HERO_SUMMARY}</p>
3937

4038
<div className={HOME_INSET}>
4139
<LandingHeroHeader
@@ -44,9 +42,9 @@ export function Hero() {
4442
eyebrow={<HeroAnnouncementChip />}
4543
heading={
4644
<>
47-
Build and govern
45+
{HOME_HERO_HEADING[0]}
4846
<br />
49-
every AI agent in one place
47+
{HOME_HERO_HEADING[1]}
5048
</>
5149
}
5250
description='Build, deploy, and manage AI agents with central control over access, spend, and data.'

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

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,42 @@ export const HOME_PAGE_DESCRIPTION = `Sim is the open-source AI workspace where
3232
*/
3333
export const HOME_PAGE_TITLE = 'The AI Workspace | Build, Distribute & Govern AI Agents | Sim'
3434

35+
/** The hero's `<h1>`, one entry per rendered line. */
36+
export const HOME_HERO_HEADING = ['Build and govern', 'every AI agent in one place'] as const
37+
38+
/**
39+
* The hero's summary paragraph, shared with the homepage's Markdown representation (`/index.md`).
40+
*/
41+
export const HOME_HERO_SUMMARY = `Sim is the open-source AI workspace where teams build, deploy, and manage AI agents for their organization. Connect ${INTEGRATION_COUNT_LABEL} integrations and every major LLM, then govern access, spend, data, and deployment from one place. Build visually, conversationally, or with code, and run Sim in your own cloud.`
42+
43+
/**
44+
* The product's feature claims, shared by the JSON-LD `featureList` below and the homepage's
45+
* Markdown representation (`/index.md`).
46+
*/
47+
export const HOME_FEATURES = [
48+
'AI workspace for teams',
49+
'Chat: build and manage agents in natural language',
50+
'Visual workflow builder',
51+
'CLI access for coding agents and terminal workflows',
52+
`${INTEGRATION_COUNT_LABEL} integrations`,
53+
'LLM orchestration (OpenAI, Anthropic, Google, xAI, Mistral, Perplexity)',
54+
'Knowledge base creation',
55+
'Table creation',
56+
'Document creation',
57+
'API access',
58+
'Custom functions',
59+
'Scheduled workflows',
60+
'Event triggers',
61+
'Permission groups for model, integration, and tool access',
62+
'Organization and per-member spend limits',
63+
'Usage analytics by workspace and member',
64+
'Agent deployment as chat, API, or MCP with SSO, email, or password access',
65+
'Single sign-on with domain verification',
66+
'Audit records and run tracing',
67+
'Configurable data retention',
68+
'Self-hosting with Docker or Kubernetes',
69+
] as const
70+
3571
const HOME_JSON_LD = {
3672
'@context': 'https://schema.org',
3773
'@graph': [
@@ -68,29 +104,7 @@ const HOME_JSON_LD = {
68104
browserRequirements: 'Requires a modern browser with JavaScript enabled',
69105
installUrl: `${SITE_URL}/signup`,
70106
publisher: { '@id': `${SITE_URL}#organization` },
71-
featureList: [
72-
'AI workspace for teams',
73-
'Chat: build and manage agents in natural language',
74-
'Visual workflow builder',
75-
'CLI access for coding agents and terminal workflows',
76-
`${INTEGRATION_COUNT_LABEL} integrations`,
77-
'LLM orchestration (OpenAI, Anthropic, Google, xAI, Mistral, Perplexity)',
78-
'Knowledge base creation',
79-
'Table creation',
80-
'Document creation',
81-
'API access',
82-
'Custom functions',
83-
'Scheduled workflows',
84-
'Event triggers',
85-
'Permission groups for model, integration, and tool access',
86-
'Organization and per-member spend limits',
87-
'Usage analytics by workspace and member',
88-
'Agent deployment as chat, API, or MCP with SSO, email, or password access',
89-
'Single sign-on with domain verification',
90-
'Audit records and run tracing',
91-
'Configurable data retention',
92-
'Self-hosting with Docker or Kubernetes',
93-
],
107+
featureList: HOME_FEATURES,
94108
},
95109
{
96110
'@type': 'SoftwareSourceCode',
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
export { HOME_PAGE_DESCRIPTION, HOME_PAGE_TITLE, HomeStructuredData } from './home-structured-data'
1+
export {
2+
HOME_FEATURES,
3+
HOME_HERO_HEADING,
4+
HOME_HERO_SUMMARY,
5+
HOME_PAGE_DESCRIPTION,
6+
HOME_PAGE_TITLE,
7+
HomeStructuredData,
8+
} from './home-structured-data'

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Metadata } from 'next'
2-
import { SITE_URL } from '@/lib/core/utils/urls'
2+
import { SITE_URL, toSiteUrl } from '@/lib/core/utils/urls'
33
import {
44
HOME_PAGE_DESCRIPTION,
55
HOME_PAGE_TITLE,
@@ -54,6 +54,7 @@ export const metadata: Metadata = {
5454
},
5555
alternates: {
5656
canonical: SITE_URL,
57+
types: { 'text/markdown': toSiteUrl('/index.md') },
5758
languages: {
5859
'en-US': SITE_URL,
5960
'x-default': SITE_URL,

‎apps/sim/app/index.md/route.ts‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { SITE_URL } from '@/lib/core/utils/urls'
2+
import { DOCS_URL } from '@/lib/help-links'
3+
import {
4+
HOME_FEATURES,
5+
HOME_HERO_HEADING,
6+
HOME_HERO_SUMMARY,
7+
HOME_PAGE_DESCRIPTION,
8+
} from '@/app/(landing)/components/home-structured-data'
9+
import { PLATFORM_MENU } from '@/app/(landing)/components/navbar/components/nav-menu-chip'
10+
import {
11+
DOCS_LINE,
12+
GITHUB_LINE,
13+
linkLine,
14+
markdownResponse,
15+
navMenuLines,
16+
PRICING_LINE,
17+
SITEMAP_LINE,
18+
SOLUTION_LINES,
19+
section,
20+
} from '@/app/llms.txt/llms'
21+
22+
export const dynamic = 'force-static'
23+
export const revalidate = 3600
24+
25+
/**
26+
* The homepage as Markdown. `next.config.ts` rewrites `/` here when `Accept` asks for
27+
* `text/markdown`, so this body is also served at `/`. It is `no-store` so a cache that ignores
28+
* `Vary` can never hand it to a browser; at worst such a cache serves the HTML to a Markdown
29+
* client.
30+
*/
31+
export async function GET() {
32+
const response = markdownResponse(
33+
[
34+
`# Sim: ${HOME_HERO_HEADING.join(' ')}`,
35+
`> ${HOME_PAGE_DESCRIPTION}`,
36+
HOME_HERO_SUMMARY,
37+
section('Get started', [
38+
linkLine('Sign up', '/signup', 'Create a free account'),
39+
PRICING_LINE,
40+
DOCS_LINE,
41+
GITHUB_LINE,
42+
]),
43+
section('Build with code', [
44+
linkLine(
45+
'API quickstart',
46+
`${DOCS_URL}/api-reference/getting-started`,
47+
'Create an API key and run a deployed workflow'
48+
),
49+
linkLine(
50+
'TypeScript SDK',
51+
`${DOCS_URL}/api-reference/typescript`,
52+
'`bun add simstudio-ts-sdk`'
53+
),
54+
linkLine('Python SDK', `${DOCS_URL}/api-reference/python`, '`pip install simstudio-sdk`'),
55+
linkLine('CLI', `${DOCS_URL}/cli`, '`bun add -g sim`'),
56+
linkLine('MCP server', `${DOCS_URL}/mcp/overview`, 'Connect an AI app to Sim'),
57+
]),
58+
section(
59+
'What teams do in Sim',
60+
HOME_FEATURES.map((feature) => `- ${feature}`)
61+
),
62+
section('Platform', navMenuLines(PLATFORM_MENU)),
63+
section('Solutions', SOLUTION_LINES),
64+
section('More', [
65+
linkLine('llms.txt', '/llms.txt', 'An index of every public page'),
66+
linkLine(
67+
'llms-full.txt',
68+
'/llms-full.txt',
69+
'Full text of the library, customer stories, and comparisons'
70+
),
71+
SITEMAP_LINE,
72+
]),
73+
],
74+
revalidate
75+
)
76+
response.headers.set('Cache-Control', 'no-store')
77+
response.headers.set('Link', `<${SITE_URL}>; rel="canonical"`)
78+
return response
79+
}

‎apps/sim/app/llms.txt/llms.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { INTEGRATION_REACH } from '@sim/deployment-config/integration-claims'
22
import { SITE_URL, toSiteUrl } from '@/lib/core/utils/urls'
3+
import { DOCS_URL } from '@/lib/help-links'
34
import type { NavMenu } from '@/app/(landing)/components/navbar/components/nav-menu-chip'
45
import { COMPLIANCE_PAGE_DESCRIPTION } from '@/app/(landing)/solutions/compliance/compliance'
56
import { ENGINEERING_PAGE_DESCRIPTION } from '@/app/(landing)/solutions/engineering/engineering'
@@ -36,6 +37,20 @@ const SOLUTIONS = [
3637
{ title: 'AI agents for sales', path: '/solutions/sales', description: SALES_PAGE_DESCRIPTION },
3738
] as const
3839

40+
/** Link lines both `/llms.txt` and the homepage's Markdown (`/index.md`) list. */
41+
export const PRICING_LINE = linkLine('Pricing', '/pricing', 'Free, Pro, Max, and Enterprise plans')
42+
export const DOCS_LINE = linkLine('Documentation', DOCS_URL, 'Guides, SDKs, and API reference')
43+
export const GITHUB_LINE = linkLine(
44+
'GitHub',
45+
'https://github.com/simstudioai/sim',
46+
'Open-source codebase'
47+
)
48+
export const SITEMAP_LINE = linkLine(
49+
'Sitemap',
50+
'/sitemap.xml',
51+
'Every public URL, including each integration and model'
52+
)
53+
3954
/** One llms.txt entry per `/solutions/*` page. */
4055
export const SOLUTION_LINES = SOLUTIONS.map((s) => linkLine(s.title, s.path, s.description))
4156

‎apps/sim/app/llms.txt/route.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@ import { ALL_COMPETITORS } from '@/app/(landing)/comparisons/utils'
1212
import { PLATFORM_MENU } from '@/app/(landing)/components/navbar/components/nav-menu-chip'
1313
import { MODEL_PROVIDERS_WITH_CATALOGS } from '@/app/(landing)/models/utils'
1414
import {
15+
DOCS_LINE,
16+
GITHUB_LINE,
1517
LLMS_HEADER,
1618
linkLine,
1719
markdownResponse,
1820
navMenuLines,
21+
PRICING_LINE,
22+
SITEMAP_LINE,
1923
SOLUTION_LINES,
2024
section,
2125
} from '@/app/llms.txt/llms'
@@ -44,7 +48,7 @@ export async function GET() {
4448
section('Platform', [
4549
linkLine('Home', '/', 'Product overview and primary entry point'),
4650
...navMenuLines(PLATFORM_MENU),
47-
linkLine('Pricing', '/pricing', 'Free, Pro, Max, and Enterprise plans'),
51+
PRICING_LINE,
4852
]),
4953
section('Solutions', SOLUTION_LINES),
5054
section(
@@ -98,24 +102,20 @@ export async function GET() {
98102
`${DOCS_URL}/llms-full.txt`,
99103
'Full text of the Sim documentation'
100104
),
101-
linkLine('Documentation', DOCS_URL, 'Guides, SDKs, and API reference'),
105+
DOCS_LINE,
102106
]),
103107
section('Blog', [
104108
linkLine('Blog', '/blog', 'Announcements, engineering deep dives, and product context'),
105109
...blogPosts.map((p) => linkLine(p.title, p.canonical, p.description)),
106110
]),
107111
section('Optional', [
108112
linkLine('Changelog', '/changelog', 'Product updates and release notes'),
109-
linkLine('GitHub', 'https://github.com/simstudioai/sim', 'Open-source codebase'),
113+
GITHUB_LINE,
110114
linkLine('Community Slack', SLACK_COMMUNITY_URL, 'Community workspace'),
111115
linkLine('Terms of Service', '/terms'),
112116
linkLine('Privacy Policy', '/privacy'),
113117
linkLine('Cookie Policy', '/cookie-policy'),
114-
linkLine(
115-
'Sitemap',
116-
'/sitemap.xml',
117-
'Every public URL, including each integration and model'
118-
),
118+
SITEMAP_LINE,
119119
]),
120120
],
121121
revalidate

‎apps/sim/next.config.ts‎

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@ import {
1212
import { LANDING_ROUTES } from './lib/landing/routes'
1313
import { LIBRARY_MERGED_SLUGS, LIBRARY_MOVED_BLOG_SLUGS } from './lib/library/retired-slugs'
1414

15+
/**
16+
* `Accept` header patterns for Markdown negotiation. `text/markdown` must be one of the listed
17+
* media ranges, and `q=0` (RFC 9110 §12.4.2: not acceptable) keeps the HTML page. Next anchors
18+
* both patterns to the whole header.
19+
*/
20+
const ACCEPTS_MARKDOWN = '(.*,)?\\s*text/markdown\\s*([;,].*)?'
21+
const REFUSES_MARKDOWN =
22+
'(.*,)?\\s*text/markdown(\\s*;[^,]*?)?\\s*;\\s*q\\s*=\\s*0(\\.0{0,3})?\\s*(,.*)?'
23+
const MARKDOWN_REQUEST = {
24+
has: [{ type: 'header' as const, key: 'accept', value: ACCEPTS_MARKDOWN }],
25+
missing: [{ type: 'header' as const, key: 'accept', value: REFUSES_MARKDOWN }],
26+
}
27+
1528
const nextConfig: NextConfig = {
1629
devIndicators: false,
1730
poweredByHeader: false,
@@ -264,6 +277,11 @@ const nextConfig: NextConfig = {
264277
],
265278
async headers() {
266279
return [
280+
{
281+
/** `/` answers in HTML or Markdown by `Accept` (see the `beforeFiles` rewrite). */
282+
source: '/',
283+
headers: [{ key: 'Vary', value: 'Accept' }],
284+
},
267285
{
268286
// `/public`-served assets keep their path across deploys (no content
269287
// hash), so a shorter TTL + revalidation window bounds how long a
@@ -669,7 +687,8 @@ const nextConfig: NextConfig = {
669687
},
670688
async rewrites() {
671689
return {
672-
beforeFiles: [],
690+
/** `beforeFiles` so a Markdown client's `/` reaches `/index.md` ahead of the HTML page. */
691+
beforeFiles: [{ source: '/', ...MARKDOWN_REQUEST, destination: '/index.md' }],
673692
afterFiles: [
674693
{
675694
source: '/favicon.ico',
@@ -683,21 +702,11 @@ const nextConfig: NextConfig = {
683702
/**
684703
* Fallback rewrites run only after every page and dynamic route has failed to match, so
685704
* this answers a Markdown client's 404 in Markdown without shadowing any real route.
686-
* `text/markdown` must be one of the listed media ranges, and `q=0` (RFC 9110 §12.4.2:
687-
* not acceptable) keeps the HTML page. Next anchors both patterns to the whole header.
688705
*/
689706
fallback: [
690707
{
691708
source: '/:path*',
692-
has: [{ type: 'header', key: 'accept', value: '(.*,)?\\s*text/markdown\\s*([;,].*)?' }],
693-
missing: [
694-
{
695-
type: 'header',
696-
key: 'accept',
697-
value:
698-
'(.*,)?\\s*text/markdown(\\s*;[^,]*?)?\\s*;\\s*q\\s*=\\s*0(\\.0{0,3})?\\s*(,.*)?',
699-
},
700-
],
709+
...MARKDOWN_REQUEST,
701710
destination: '/markdown-not-found',
702711
},
703712
],

0 commit comments

Comments
 (0)