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) +}