Open source · Apache 2.0
rules_sitemap
Bazel rules that generate sitemap.xml, a sitemap index and robots.txt for a statically built site, and fail the build when the result would be invalid.
Source: github.com/theluckystrike/rules_sitemap. Module name theluckystrike_rules_sitemap. Pure Starlark: no toolchain, no host tools, no network access, and no transitive dependencies for consumers.
Why it exists
A sitemap is usually the last artefact in a static-site build and the least tested one. The failure mode is quiet. The file is served with HTTP 200, the crawler parses what it can, and the entries it rejects simply never appear in the index. Nothing in the build went red.
The three mistakes that cause it are all mechanical:
- an unescaped
&in a query string, which makes the document malformed XML; - a
Sitemap:line inrobots.txtstill pointing at the previous origin, because that one directive has to be an absolute URL; - duplicate
<loc>entries, or more than 50,000 of them in a single file.
These rules turn each of those into a build error instead of a silent deletion from the index.
Install
bazel_dep(name = "theluckystrike_rules_sitemap", version = "0.1.0")
Use
load("@theluckystrike_rules_sitemap//sitemap:defs.bzl", "robots_txt", "sitemap_xml")
PAGES = [
"/",
"/pricing/",
"/docs/getting-started/",
]
sitemap_xml(
name = "sitemap",
base_url = "https://example.com",
pages = PAGES,
lastmod = "2026-08-09",
changefreq = "weekly",
out = "sitemap.xml",
)
robots_txt(
name = "robots",
base_url = "https://example.com",
allow = ["/"],
disallow = ["/admin/"],
sitemaps = ["/sitemap.xml"],
out = "robots.txt",
)
Page paths are root-relative and are joined onto base_url, so the same page list produces a correct sitemap for a staging origin and for production. Only base_url changes.
What is rejected at analysis time
| Input | Error |
|---|---|
base_url = "example.com" | base_url must start with http:// or https:// |
pages = ["about/"] | page paths must be root-relative and start with '/' |
| the same path listed twice | duplicate page path |
| more than 50,000 pages in one sitemap | protocol limit, with a pointer to sitemap_index |
changefreq = "fortnightly" | changefreq must be one of always, hourly, daily, weekly, monthly, yearly, never |
priority = "1.5" | priority must be between 0.0 and 1.0 |
lastmod = "09/08/2026" | lastmod must be a W3C Datetime such as 2026-08-09 |
disallow = ["draft/"] | disallow paths must be root-relative and start with '/' |
Rules
| Rule | Output |
|---|---|
sitemap_xml | A sitemaps.org urlset document, with optional lastmod, changefreq and priority applied to every entry. |
sitemap_index | A sitemaps.org sitemapindex document, for sites split across several sitemaps. |
robots_txt | A robots.txt whose Sitemap: references are made absolute from base_url. |
Tests
bazel test //...
19 tests: Starlark unit tests for the rendering helpers, golden byte-comparison tests for each rule, and one analysis test per rejected input in the table above. examples/site is a standalone Bazel module that builds a three-page site and byte-compares both generated files against checked-in expected output.
Scope
The rules deliberately do not crawl a directory of HTML files to discover pages. Which URLs belong in a sitemap is a routing decision only the site generator knows — trailing slashes, pagination, canonical duplicates, noindex pages — so the page list is an explicit input. Generate it from whatever produces your routes and pass it in.
Licence
Apache License 2.0. Written and maintained by Michael Lip.