SEO & Security.txt
Every Kyrux project ships with the built-in apps/meta app, installed
automatically (already listed in core/apps/installed.go) — no need to run
startapp or register anything by hand. It serves three files that almost
every public site needs and that, without this, every project ends up
reimplementing from scratch:
| Route | What it is |
|---|---|
GET /robots.txt |
Allows full indexing (Allow: /) and points to the sitemap |
GET /sitemap.xml |
List of indexable pages |
GET /.well-known/security.txt |
Security contact, RFC 9116 format |
GET /security.txt |
Redirects (301) to /.well-known/security.txt — some older scanners still check the root first |
Separation: routes.go only maps paths, views.go has the content
apps/meta/
├── routes.go ← router.Path("GET", "/robots.txt", views.RobotsView(fw), ...)
└── views/
└── views.go ← content, XML/text assembly, fallback without APP_URL
This separation isn't cosmetic: routes.go stays readable as an index of
the project's URLs — to know what a route does, you read the view, not
string-building logic buried in the middle of the route list.
APP_URL — the canonical origin
# .env
APP_URL=https://mysite.com
APP_URL (no trailing slash) is used to build the absolute URLs for
Sitemap: in robots.txt, <loc> in sitemap.xml, and Canonical: in
security.txt. Without it configured, all three files still work — they
just omit the fields that would depend on the domain (sitemap.xml ships
with a relative <loc>, robots.txt without the Sitemap: line,
security.txt without Canonical:). Configure it before going to
production.
Customizing the content
The three handlers live in apps/meta/views/views.go — edit them directly:
// sitemapPaths lists the project's public pages. The framework doesn't
// discover this on its own from registered routes — not every GET route
// is a page worth indexing (JSON endpoints, parameterized routes, etc).
var sitemapPaths = []string{
"/",
"/about/",
"/contact/",
}
// securityContact and securityExpires — replace with the project's real
// security contact and review Expires every year (RFC 9116: past that
// date, scanners go back to reporting the file as missing/invalid even
// though it exists).
const (
securityContact = "mailto:[email protected]"
securityExpires = "2027-01-01T00:00:00.000Z"
)
If the project already has a source of truth for its own pages — a slice
of sections, a query result, the filtered output of Router.Routes() —
replace the fixed sitemapPaths slice with a function that builds the
list from it, instead of keeping the two manually in sync. That's exactly
what this site does: its sitemap.xml is built from content.All(), the
same source that feeds the documentation navigation — every new section
under apps/docs/content/markdown/ shows up in the sitemap without
touching views.go.
Blocking an area in robots.txt
By default RobotsView allows everything (Allow: /). To restrict an
area (e.g. /admin/), edit the body built in RobotsView — there's no
config flag for it, just a string.