Skip to content

Commit 4fbdaa1

Browse files
committed
fix(proxy): noindex llms.txt, llms-full.txt and changelog.xml on self-hosted deployments
1 parent 2de9984 commit 4fbdaa1

3 files changed

Lines changed: 37 additions & 4 deletions

File tree

‎apps/sim/lib/landing/routes.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
* landing footer's toggle, instead of the app's account-synced `system`
1616
* default. Leave one out and a visitor on a dark OS lands on a dark
1717
* marketing page.
18-
* - The proxy marks these paths `noindex` on self-hosted deployments, so a
18+
* - The proxy marks these paths, and the marketing documents in
19+
* {@link isMarketingPath}, `noindex` on self-hosted deployments, so a
1920
* third-party copy of the marketing site never competes with sim.ai in search
2021
* or answer engines. Leave one out and that page stays indexable there.
2122
*
@@ -56,3 +57,19 @@ export function isLandingPath(pathname: string): boolean {
5657
const firstSegment = pathname.split('/')[1]
5758
return firstSegment === '' || LANDING_SEGMENTS.has(firstSegment)
5859
}
60+
61+
/**
62+
* Root-level documents that republish the marketing site's content for crawlers
63+
* and answer engines. They are served outside `app/(landing)`, so
64+
* {@link isLandingPath} does not cover them.
65+
*/
66+
const MARKETING_DOCUMENT_PATHS: ReadonlySet<string> = new Set([
67+
'/llms.txt',
68+
'/llms-full.txt',
69+
'/changelog.xml',
70+
])
71+
72+
/** Whether a pathname serves marketing content: a landing page or a marketing document. */
73+
export function isMarketingPath(pathname: string): boolean {
74+
return isLandingPath(pathname) || MARKETING_DOCUMENT_PATHS.has(pathname)
75+
}

‎apps/sim/proxy.test.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,20 @@ describe('proxy indexing policy', () => {
173173
expect(robotsTag('/pricing')).toBe('noindex, nofollow')
174174
})
175175

176-
it('leaves the marketing pages of the hosted deployment indexable', () => {
176+
it('keeps the marketing documents of a self-hosted deployment out of search results', () => {
177+
setEnvFlags({ isHosted: false })
178+
expect(robotsTag('/llms.txt')).toBe('noindex, nofollow')
179+
expect(robotsTag('/llms-full.txt')).toBe('noindex, nofollow')
180+
expect(robotsTag('/changelog.xml')).toBe('noindex, nofollow')
181+
})
182+
183+
it('leaves the marketing pages and documents of the hosted deployment indexable', () => {
177184
setEnvFlags({ isHosted: true })
178185
expect(robotsTag('/comparisons/n8n')).toBeNull()
179186
expect(robotsTag('/pricing')).toBeNull()
187+
expect(robotsTag('/llms.txt')).toBeNull()
188+
expect(robotsTag('/llms-full.txt')).toBeNull()
189+
expect(robotsTag('/changelog.xml')).toBeNull()
180190
})
181191

182192
it('leaves a self-hosted non-landing public page indexable', () => {
@@ -199,6 +209,12 @@ describe('proxy matcher', () => {
199209
expect(matches('/api/internal/project-file-doc/project-1/file-1/persist')).toBe(false)
200210
})
201211

212+
it('runs for the marketing documents, so the indexing policy reaches them', () => {
213+
expect(matches('/llms.txt')).toBe(true)
214+
expect(matches('/llms-full.txt')).toBe(true)
215+
expect(matches('/changelog.xml')).toBe(true)
216+
})
217+
202218
it('still runs for every other API route', () => {
203219
expect(matches('/api/desktop/tool/claim')).toBe(true)
204220
expect(matches('/api/desktop/tool/import/other')).toBe(true)

‎apps/sim/proxy.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getSessionCookie } from 'better-auth/cookies'
77
import { type NextRequest, NextResponse } from 'next/server'
88
import { resolveSimMcpHostPath } from '@/lib/api/mcp/host-routing'
99
import { SIM_MCP_ROUTE_PATH } from '@/lib/api/mcp/urls'
10-
import { isLandingPath } from '@/lib/landing/routes'
10+
import { isMarketingPath } from '@/lib/landing/routes'
1111
import { APP_ENTRY_PATH, isAppSurfacePath, isNoindexPath } from '@/lib/navigation/paths'
1212
import { isOAuthAuthorizationCallback, resolveAuthRedirect } from '@/app/(auth)/auth-redirect'
1313
import { getEnv } from './lib/core/config/env'
@@ -457,7 +457,7 @@ function applyIndexingPolicy(request: NextRequest, response: NextResponse): Next
457457
if (
458458
isNonCanonicalSimHost(host) ||
459459
isNoindexPath(pathname) ||
460-
(!isHosted && isLandingPath(pathname))
460+
(!isHosted && isMarketingPath(pathname))
461461
) {
462462
response.headers.set('X-Robots-Tag', 'noindex, nofollow')
463463
}

0 commit comments

Comments
 (0)