Skip to content

Serve robots.txt and a sitemap that lists every tool - #175

Merged
vibhavkatre merged 1 commit into
developfrom
crawl-directives
Aug 10, 2026
Merged

Serve robots.txt and a sitemap that lists every tool#175
vibhavkatre merged 1 commit into
developfrom
crawl-directives

Conversation

@vibhavkatre

Copy link
Copy Markdown
Collaborator

Closes #173. Follows #174.

Both files exist on the deployed site today. Both are useless.

/robots.txt returns 200 with Content-Length: 0. Frappe reads it from a Website Settings field, which is empty. That field is site data, so a value typed into the desk would have to be typed again on the next deployment, and nothing in the repository would record what it said.

/sitemap.xml returns Frappe's stock pages and not one tool:

<url><loc>http://toolbox.localhost:8100/about</loc></url>
<url><loc>http://toolbox.localhost:8100/contact</loc></url>

Frappe builds it from its own page list and from DocTypes with a web view. Toolbox tools are website route rules, so none of them qualify. All fifteen were invisible to anything reading the sitemap.

After

$ curl -s https://frappe.tools/sitemap.xml | grep -c '<loc>'
16                                     # the root and 15 tools; /settings excluded

$ curl -s https://frappe.tools/robots.txt
User-agent: *
Disallow: /api/
Disallow: /app/

Sitemap: https://frappe.tools/sitemap.xml

Both are generated from toolbox/routes.py. The override holds because TemplatePage searches installed apps in reverse order and Toolbox is installed after Frappe — an e2e test asserts the served result rather than the mechanism, so the day that stops being true, it fails.

Two decisions that look like omissions

No Allow: /. Anything not disallowed is already allowed, and the line is not harmless. Google resolves a conflict by longest matching path, but several parsers take the first match — including Python's own robotparser. Under those, a leading blanket allow wins every comparison and voids every Disallow beneath it. This was measured, not assumed: with the line present, /api/method/toolbox.weather.forecast parsed as crawlable. test_it_declares_no_blanket_allow is the guard against putting it back.

/settings is not disallowed. It carries noindex from #174 and it is linked from the navigation. A crawler blocked from fetching a page never reads the noindex on it, so the URL can still surface in results with no description. Blocking and hiding are different instructions, and only one of them belongs on a page we link to.

No lastmod. Frappe's sitemap stamps every entry with today's date, every day. A date that is always wrong is worse than none: a crawler that stops trusting it discounts the real ones later. The sitemap protocol makes the field optional.

Consistency with #174

The sitemap lists the routes seo.py marks indexable, which is the same flag that writes the robots meta tag. A page cannot be told to stay out of the index and be advertised for crawling in the same breath, and test_the_count_matches_what_seo_declares_indexable holds the two together.

Verified

Live on toolbox.localhost:8100: /sitemap.xml is valid XML under ElementTree with 16 URLs and correct application/xml; /robots.txt is text/plain, parses under RobotFileParser, declares the sitemap, and returns the intended verdict for each path — tools crawlable, /settings crawlable, /api/ and /app/ blocked.

backend 169 (was 158) · vitest 610 · e2e 195 passed / 6 skipped (was 189/6) · build clean.

ruff and pre-commit are not installed in this bench, so the Python was checked against the project's conventions by hand rather than by running the hooks.

Frappe answers /robots.txt from a Website Settings field. That field is empty,
so the site served a 200 with no body. The value is site data rather than
repository data, so anything typed into the desk would have to be typed again on
the next deployment, and nothing in the repository would record what it said.

Frappe answers /sitemap.xml from its own page list and from DocTypes with a web
view. Toolbox tools are website route rules, so none of them appeared. The
sitemap offered exactly two URLs, Frappe's stock /about and /contact, and hid
all fifteen tools.

Both files now come from the app, built from toolbox/routes.py. The override
holds because TemplatePage searches installed apps in reverse order and Toolbox
is installed after Frappe.

The sitemap lists the routes seo.py marks indexable, which is the same flag that
writes the robots meta tag, so a page cannot be told to stay out of the index
and be advertised for crawling in the same breath. That is why /settings is
absent. There is no lastmod: Frappe's sitemap stamps every entry with today's
date, every day, and a date that is always wrong teaches a crawler to discount
the real ones later.

robots.txt carries no "Allow: /" line. Anything not disallowed is already
allowed, and the line would not be harmless. Google resolves a conflict by the
longest matching path, but several parsers take the first match, including
Python's own robotparser; under those a leading blanket allow wins every
comparison and voids every Disallow below it. Measured before the line was
removed: /api/method/... read as crawlable.

/settings is not disallowed either. It carries noindex and is linked from the
navigation, and a crawler that is blocked from fetching a page never reads the
noindex on it, so the URL can still surface with no description at all.

backend 169, vitest 610, e2e 195 passed and 6 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vibhavkatre
vibhavkatre merged commit 84fc6df into develop Aug 10, 2026
@vibhavkatre
vibhavkatre deleted the crawl-directives branch August 10, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Serve robots.txt and a sitemap that lists every tool

1 participant