Kyrux
Back

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.