ReviewOS

also looking at this

stacks/commshq

fix(seo): ship a robots.txt so the production smoke test passes

#10
Merged glennmichael123 wants to merge fix/robots-txt into main
2 files +46 -0
public/robots.txtadded+24-0
Changes to public/robots.txt
@@ -0,0 +1,24 @@
1# https://commshq.org — crawl policy.
2#
3# The auth-walled routes below also send `robots: noindex, nofollow` from their
4# own useSeoMeta call, which is what keeps them out of an index once fetched.
5# This file stops well-behaved crawlers spending the request at all, and is the
6# only place that can speak for routes that never render HTML.
7#
8# No sitemap is published yet (sitemap.xml is a 404). Add a Sitemap: line here
9# when one exists rather than pointing crawlers at a missing file.
10
11User-agent: *
12
13Disallow: /dashboard
14Disallow: /login
15Disallow: /register
16
17# Landing pages reached from email links; nothing worth indexing.
18Disallow: /preferences-saved
19Disallow: /subscription-confirmed
20
21# The JSON API: no HTML, nothing to index.
22Disallow: /api/
23
24Allow: /
tests/Unit/RobotsTxt.test.tsadded+22-0
Changes to tests/Unit/RobotsTxt.test.ts
@@ -0,0 +1,22 @@
1import { describe, expect, it } from 'bun:test'
2import { readFileSync } from 'node:fs'
3import { join } from 'node:path'
4
5const robots = readFileSync(join(import.meta.dir, '../../public/robots.txt'), 'utf8')
6
7// scripts/smoke-deployment.ts fetches /robots.txt on production after every
8// deploy and fails the run if it is missing, names the wrong host, or leaks a
9// localhost URL. Checking the same things here keeps that gate from going red
10// only after the code is already live.
11describe('robots.txt', () => {
12 it('names the production host and no local one', () => {
13 expect(robots).toContain('commshq.org')
14 expect(robots).not.toContain('localhost')
15 })
16
17 it('keeps crawlers out of the auth-walled and API routes', () => {
18 for (const path of ['/dashboard', '/login', '/register', '/api/'])
19 expect(robots).toContain(`Disallow: ${path}`)
20 expect(robots).toContain('Allow: /')
21 })
22})