diff --git a/.changeset/docs-sitemap.md b/.changeset/docs-sitemap.md
new file mode 100644
index 00000000000..92c83733121
--- /dev/null
+++ b/.changeset/docs-sitemap.md
@@ -0,0 +1,5 @@
+---
+"@kilocode/kilo-docs": patch
+---
+
+Publish a sitemap at `/docs/sitemap.xml` so search engines can discover all documentation pages.
diff --git a/packages/kilo-docs/__tests__/sitemap.test.ts b/packages/kilo-docs/__tests__/sitemap.test.ts
new file mode 100644
index 00000000000..ba2a0161a10
--- /dev/null
+++ b/packages/kilo-docs/__tests__/sitemap.test.ts
@@ -0,0 +1,38 @@
+import { describe, it, expect } from "vitest"
+import { buildSitemapXml } from "../pages/api/sitemap.xml"
+
+describe("sitemap.xml", () => {
+ it("produces valid XML with urlset root", () => {
+ const xml = buildSitemapXml()
+ expect(xml).toMatch(/^<\?xml version="1\.0" encoding="UTF-8"\?>/)
+ expect(xml).toContain('')
+ expect(xml).toContain("")
+ })
+
+ it("all values start with https://kilo.ai/docs", () => {
+ const xml = buildSitemapXml()
+ const locs = [...xml.matchAll(/(.*?)<\/loc>/g)].map((m) => m[1])
+ expect(locs.length).toBeGreaterThan(0)
+ for (const loc of locs) {
+ expect(loc).toMatch(/^https:\/\/kilo\.ai\/docs/)
+ }
+ })
+
+ it("includes the docs root URL", () => {
+ const xml = buildSitemapXml()
+ expect(xml).toContain("https://kilo.ai/docs")
+ })
+
+ it("includes known pages from the nav", () => {
+ const xml = buildSitemapXml()
+ expect(xml).toContain("https://kilo.ai/docs/getting-started/installing")
+ expect(xml).toContain("https://kilo.ai/docs/getting-started")
+ })
+
+ it("has no duplicate entries", () => {
+ const xml = buildSitemapXml()
+ const locs = [...xml.matchAll(/(.*?)<\/loc>/g)].map((m) => m[1])
+ const unique = new Set(locs)
+ expect(locs.length).toBe(unique.size)
+ })
+})
diff --git a/packages/kilo-docs/next.config.js b/packages/kilo-docs/next.config.js
index 3a8835980ff..bc017eda4b1 100644
--- a/packages/kilo-docs/next.config.js
+++ b/packages/kilo-docs/next.config.js
@@ -30,6 +30,11 @@ module.exports = withMarkdoc(/* config: https://markdoc.io/docs/nextjs#options *
source: "/llms.txt",
destination: "/api/llms.txt",
},
+ {
+ // Rewrite /docs/sitemap.xml to the API endpoint (internal to basePath)
+ source: "/sitemap.xml",
+ destination: "/api/sitemap.xml",
+ },
],
afterFiles: [
{
diff --git a/packages/kilo-docs/pages/api/sitemap.xml.ts b/packages/kilo-docs/pages/api/sitemap.xml.ts
new file mode 100644
index 00000000000..76b67a40f22
--- /dev/null
+++ b/packages/kilo-docs/pages/api/sitemap.xml.ts
@@ -0,0 +1,35 @@
+import type { NextApiRequest, NextApiResponse } from "next"
+import { Nav } from "../../lib/nav"
+import type { NavLink, NavSection } from "../../lib/types"
+
+const origin = "https://kilo.ai"
+const base = "/docs"
+
+function links(sections: NavSection[]): string[] {
+ const out: string[] = []
+ function walk(items: NavLink[]) {
+ for (const item of items) {
+ out.push(item.href)
+ if (item.subLinks) walk(item.subLinks)
+ }
+ }
+ for (const s of sections) walk(s.links)
+ return out
+}
+
+export function buildSitemapXml(): string {
+ const all = Object.values(Nav).flatMap(links)
+ const hrefs = Array.from(new Set(["", ...all])).sort()
+ const urls = hrefs.map((h) => ` ${origin}${base}${h}`).join("\n")
+ return `
+
+${urls}
+`
+}
+
+export default function handler(_: NextApiRequest, res: NextApiResponse) {
+ const xml = buildSitemapXml()
+ res.setHeader("Content-Type", "application/xml; charset=utf-8")
+ res.setHeader("Cache-Control", "public, s-maxage=3600, stale-while-revalidate=86400")
+ res.status(200).send(xml)
+}